App-GHGen
view release on metacpan or search on metacpan
scripts/generate_index.pl view on Meta::CPAN
$total_info->{subroutine}{percentage} // 0,
$total_pct
);
}
Readonly my $commit_url => "https://github.com/$config{github_user}/$config{github_repo}/commit/$commit_sha";
my $short_sha = substr($commit_sha, 0, 7);
push @html, '</tbody></table>';
# Parse historical snapshots
my @trend_points;
foreach my $file (sort @history_files) {
my $json = $historical_cache{$file};
next unless $json->{summary}{Total};
my $pct = $json->{summary}{Total}{total}{percentage} // 0;
my ($date) = $file =~ /(\d{4}-\d{2}-\d{2})/;
if(defined($date)) {
push @trend_points, { date => $date, coverage => sprintf('%.1f', $pct) };
}
}
# Inject chart if we have data
my %commit_times;
my $log_output = run_git('log', '--all', '--pretty=format:%H %h %ci');
if ($log_output) {
for my $line (split /\n/, $log_output) {
my ($full_sha, $short_sha, $datetime) = split ' ', $line, 3;
$commit_times{$short_sha} = $datetime if $short_sha;
}
}
my %commit_messages;
$log_output = run_git('log', '--pretty=format:%h %s');
if ($log_output) {
for my $line (split /\n/, $log_output) {
my ($short_sha, $message) = $line =~ /^(\w+)\s+(.*)$/;
if ($message && $message =~ /^Merge branch /) {
delete $commit_times{$short_sha};
} else {
$commit_messages{$short_sha} = $message if $message;
}
}
}
# Collect data points from non-merge commits
my @data_points_with_time;
my $processed_count = 0;
foreach my $file (reverse sort @history_files) {
last if $processed_count >= $config{max_points};
my $json = $historical_cache{$file};
next unless $json->{summary}{Total};
my ($sha) = $file =~ /-(\w{7})\.json$/;
next unless $commit_messages{$sha}; # Skip merge commits
my $timestamp = $commit_times{$sha} // strftime('%Y-%m-%dT%H:%M:%S', localtime((stat($file))->mtime));
# Git log returns format like: "2024-01-15 14:30:45 -0500" or "2024-01-15 14:30:45 +0000"
# We need ISO 8601 format: "2024-01-15T14:30:45-05:00"
# Replace space between date and time with 'T'
$timestamp =~ s/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})/$1T$2/;
# Fix timezone format: convert "-0500" to "-05:00" or " -05:00" to "-05:00"
$timestamp =~ s/\s*([+-])(\d{2}):?(\d{2})$/$1$2:$3/;
# Remove any remaining spaces (safety cleanup)
$timestamp =~ s/\s+//g;
my $pct = $json->{summary}{Total}{total}{percentage} // 0;
my $color = 'gray'; # Will be set properly after sorting
my $url = "https://github.com/$config{github_user}/$config{github_repo}/commit/$sha";
my $comment = $commit_messages{$sha};
# Store with timestamp for sorting
push @data_points_with_time, {
timestamp => $timestamp,
pct => $pct,
url => $url,
comment => $comment
};
$processed_count++;
}
# Sort by timestamp to ensure chronological order
@data_points_with_time = sort { $a->{timestamp} cmp $b->{timestamp} } @data_points_with_time;
# Now calculate deltas and create JavaScript data points
my @data_points;
my $prev_pct;
foreach my $point (@data_points_with_time) {
my $delta = defined $prev_pct ? sprintf('%.1f', $point->{pct} - $prev_pct) : 0;
$prev_pct = $point->{pct};
my $color = $delta > 0 ? 'green' : $delta < 0 ? 'red' : 'gray';
my $comment = js_escape($point->{comment});
push @data_points, qq{{ x: "$point->{timestamp}", y: $point->{pct}, delta: $delta, url: "$point->{url}", label: "$point->{timestamp}", pointBackgroundColor: "$color", comment: "$comment" }};
}
my $js_data = join(",\n", @data_points);
if(scalar(@data_points)) {
push @html, <<'HTML';
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1em;">
<div>
<h2>Coverage Trend</h2>
<label>
<input type="checkbox" id="toggleTrend" checked>
Show regression trend
</label>
<div>
</div>
</div>
scripts/generate_index.pl view on Meta::CPAN
for my $r (@fail_reports) {
my $key = make_key($r);
if(!exists $best{$key} || (!$best{$key}{guid} && $r->{guid})) {
$best{$key} = $r;
}
}
my @deduped = values %best;
my %prev_fail_set;
for my $r (@prev_fail_reports) {
my $key = make_key($r);
$prev_fail_set{$key} = 1;
}
for my $r (@deduped) {
my $date = $r->{date} // '';
my $perl = $r->{perl} // '';
my $os = $r->{osname} // '';
my $grade = lc($r->{grade} // 'unknown');
my $row_class = "cpan-$grade"; # cpan-fail or cpan-unknown
my $reporter = $r->{reporter} // '';
$reporter =~ s/"//g;
$reporter =~ s/<.+>//g;
$reporter =~ s/\s+$//g;
my $guid = $r->{guid} // '';
my $url = $guid ? "https://www.cpantesters.org/cpan/report/$guid" : '#';
my $is_new = !$prev_fail_set{make_key($r)};
my $new_html = $is_new ? '<span class="new-failure">NEW</span>' : '';
push @html, sprintf(
qq{<tr class="%s"><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>
<td><a href="%s" target="_blank">View</a></td></tr>},
$row_class,
$date,
$os,
$perl,
$reporter,
$new_html,
$url
);
}
push @html, '</tbody></table>';
} else {
# @fail_reports is empty
push @html, "<p>No <A HREF=\"https://fast2-matrix.cpantesters.org/?dist=$dist_name+$version\">CPAN Testers</A> failures reported for $dist_name $version</p>";
}
} elsif($res->{status} == 404) { # 404 means no fail reports
# push @html, "<A HREF=\"$cpan_api\">$cpan_api</A>";
push @html, "<p>No CPAN Testers failures reported for $dist_name $version.</p>";
} else {
push @html, "<a href=\"$cpan_api\">$cpan_api</a>: $res->{status} $res->{reason}";
}
my $timestamp = 'Unknown';
if (my $stat = stat($config{cover_db})) {
$timestamp = strftime('%Y-%m-%d %H:%M:%S', localtime($stat->mtime));
}
push @html, <<"HTML";
<footer>
<p>Project: <a href="https://github.com/$config{github_user}/$config{github_repo}">$config{github_repo}</a></p>
<p><em>Last updated: $timestamp - <a href="$commit_url">commit <code>$short_sha</code></a></em></p>
</footer>
</body>
</html>
HTML
# Write to index.html
write_file($config{output}, join("\n", @html));
# Safe git command execution
sub run_git {
my @cmd = @_;
my ($out, $err);
run3 ['git', @cmd], \undef, \$out, \$err;
return unless $? == 0;
chomp $out;
return $out;
}
sub js_escape {
my $str = $_[0];
$str =~ s/\\/\\\\/g;
$str =~ s/"/\\"/g;
$str =~ s/\n/\\n/g;
return $str;
}
sub fetch_reports_by_grades {
my ($dist, $version, @grades) = @_;
my %seen;
my @reports;
for my $grade (@grades) {
my $url = 'https://api.cpantesters.org/v3/summary/'
. uri_escape($dist)
. '/' . uri_escape($version)
. "?grade=$grade";
my $res = $http->get($url);
next unless $res->{success};
my $arr = eval { decode_json($res->{content}) };
next unless ref $arr eq 'ARRAY';
for my $r (@$arr) {
my $key = make_key($r);
next if $seen{$key}++;
push @reports, $r;
}
}
( run in 1.184 second using v1.01-cache-2.11-cpan-39bf76dae61 )