-
-
Save xsawyerx/8323466 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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