App-ListNewCPANDists
view release on metacpan or search on metacpan
lib/App/ListNewCPANDists.pm view on Meta::CPAN
package App::ListNewCPANDists;
use 5.010001;
use strict;
use warnings;
use Log::ger;
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2023-03-28'; # DATE
our $DIST = 'App-ListNewCPANDists'; # DIST
our $VERSION = '0.023'; # VERSION
our %SPEC;
my $sch_date = ['date*', 'x.perl.coerce_to' => 'DateTime', 'x.perl.coerce_rules'=>['From_str::natural']];
our $db_schema_spec = {
summary => __PACKAGE__,
latest_v => 2,
install => [
'CREATE TABLE dist (
name TEXT NOT NULL PRIMARY KEY,
first_version TEXT NOT NULL,
first_time INTEGER NOT NULL,
latest_version TEXT NOT NULL,
latest_time INTEGER NOT NULL,
mtime INTEGER NOT NULL
)',
],
install_v1 => [
'CREATE TABLE release (
name TEXT NOT NULL PRIMARY KEY,
dist TEXT NOT NULL,
time INTEGER NOT NULL
)',
'CREATE UNIQUE INDEX ix_release__dist ON release(name,dist)',
],
upgrade_to_v2 => [
'DROP TABLE release',
'CREATE TABLE dist (
name TEXT NOT NULL PRIMARY KEY,
first_version TEXT NOT NULL,
first_time INTEGER NOT NULL,
latest_version TEXT NOT NULL,
latest_time INTEGER NOT NULL,
mtime INTEGER NOT NULL
)',
],
};
our %args_common = (
cpan => {
summary => 'Location of your local CPAN mirror, e.g. /path/to/cpan',
schema => 'dirname*',
description => <<'_',
Defaults to `~/cpan`. This actually does not need to be a real CPAN local
mirror, but can be just an empty directory. If you use happen to use
<pm:App::lcpan>, you can use the local CPAN mirror generated by <prog:lcpan>
(which also defaults to `~/cpan`) to store the database.
_
tags => ['common', 'category:local-cpan'],
},
db_name => {
summary => 'Filename of database',
schema =>'filename*',
default => 'index-lncd.db',
tags => ['common', 'category:db'],
},
);
our %args_filter = (
exclude_dists => {
'x.name.is_plural' => 1,
lib/App/ListNewCPANDists.pm view on Meta::CPAN
Monday is the start of the week.
_
tags => ['category:time-filtering'],
},
this_month => {
schema => 'true*',
tags => ['category:time-filtering'],
},
this_year => {
schema => 'true*',
tags => ['category:time-filtering'],
},
yesterday => {
schema => 'true*',
tags => ['category:time-filtering'],
},
last_week => {
schema => 'true*',
description => <<'_',
Monday is the start of the week.
_
tags => ['category:time-filtering'],
},
last_month => {
schema => 'true*',
tags => ['category:time-filtering'],
},
last_year => {
schema => 'true*',
tags => ['category:time-filtering'],
},
},
args_rels => {
req_one => [qw/today this_week this_month this_year yesterday last_week last_month last_year from_time/],
},
examples => [
{
summary => 'Show new distributions from Jan 1, 2019 to the present',
argv => ['2019-01-01'],
'x.doc.show_result' => 0,
test => 0,
},
{
summary => "Show PERLANCAR's new distributions this year",
argv => ['--include-author', 'PERLANCAR', '--this-year'],
'x.doc.show_result' => 0,
test => 0,
},
{
summary => "What are the new releases last month?",
argv => ['--last-month'],
'x.doc.show_result' => 0,
test => 0,
},
],
};
sub list_new_cpan_dists {
require DateTime;
my %args = @_;
my $state = _init(\%args);
my $dbh = $state->{dbh};
my $today = DateTime->today;
my $now = DateTime->now;
my $end_of_yesterday = $now->clone->add(days => -1)->set(hour => 23, minute => 59, second => 59);
my $to_time = $args{to_time} // $now->clone;
my $from_time;
if ($args{from_time}) {
$from_time = $args{from_time};
} elsif ($args{today}) {
$from_time = $today;
} elsif ($args{this_week}) {
my $dow = $today->day_of_week;
$from_time = $today->clone->add(days => -($dow-1));
} elsif ($args{this_month}) {
$from_time = $today->clone->set(day => 1);
} elsif ($args{this_year}) {
$from_time = $today->set(day => 1, month => 1);
} elsif ($args{yesterday}) {
$from_time = $today->add(days => -1);
$to_time = $end_of_yesterday;
} elsif ($args{last_week}) {
my $dow = $today->day_of_week;
my $start_of_last_week = $today->clone->add(days => -($dow-1))->add(days => -7);
my $end_of_last_week = $start_of_last_week->clone->add(days => 7)->add(seconds => -1);
$from_time = $start_of_last_week;
$to_time = $end_of_last_week;
} elsif ($args{last_month}) {
$from_time = $today->clone->set(day => 1)->add(months => -1);
$to_time = $today->clone->set(day => 1)->add(seconds => -1);
} elsif ($args{last_year}) {
$from_time = $today->clone->set(day => 1, month => 1)->add(years => -1);
$to_time = $today->clone->set(day => 1, month => 1)->add(seconds => -1);
} else {
return [400, "Please specify today/yesterday/{this,last}_{week,month,year}/from_time"];
}
#if (!$to_time) {
# $to_time = $from_time->clone;
# $to_time->set_hour(23);
# $to_time->set_minute(59);
# $to_time->set_second(59);
#}
if ($args{-orig_to_time} && $args{-orig_to_time} !~ /T\d\d:\d\d:\d\d/) {
$to_time->set_hour(23);
$to_time->set_minute(59);
$to_time->set_second(59);
}
log_trace("Retrieving releases from %s to %s ...",
$from_time->datetime, $to_time->datetime);
require App::MetaCPANUtils;
my $api_res = App::MetaCPANUtils::list_metacpan_releases(
from_date => $from_time,
to_date => $to_time,
fields => [qw/author date distribution abstract first/],
sort => 'release',
);
#fields => [qw/name author distribution abstract date version version_numified/],
return [500, "Can't list MetaCPAN releases: $api_res->[0] - $api_res->[1]"]
lib/App/ListNewCPANDists.pm view on Meta::CPAN
[200, "OK", \@rows, \%resmeta];
}
$SPEC{create_new_cpan_dists_stats} = {
v => 1.1,
args => {
dists => {
schema => 'array*',
},
},
};
sub create_new_cpan_dists_stats {
my %args = @_;
my $dists = $args{dists};
my %authors;
for my $dist (@$dists) {
$authors{$dist->{author}} //= {num_dists => 0};
$authors{$dist->{author}}{num_dists}++;
}
my @authors_by_num_dists = map {
+{author=>$_, num_dists=>$authors{$_}{num_dists}}
} sort { $authors{$b}{num_dists} <=> $authors{$a}{num_dists} }
keys %authors;
my $num_authors = keys %authors;
my $stats = {
"Number of new CPAN distributions this period" => scalar(@$dists),
"Number of authors releasing new CPAN distributions this period" => $num_authors,
"Authors by number of new CPAN distributions this period" => \@authors_by_num_dists,
};
[200, "OK", $stats];
}
$SPEC{list_monthly_new_cpan_dists} = {
v => 1.1,
summary => 'List new CPAN distributions in a given month',
description => <<'_',
Like `list_new_cpan_dists` but you only need to specify month and year instead
of starting and ending time period.
_
args => {
%args_filter,
month => {
schema => ['int*', min=>1, max=>12],
req => 1,
pos => 0,
},
year => {
schema => ['int*', min=>1990, max=>9999],
req => 1,
pos => 1,
},
},
};
sub list_monthly_new_cpan_dists {
require DateTime;
require Time::Local;
my %args = @_;
my $mon = delete $args{month};
my $year = delete $args{year};
my $from_time = Time::Local::timegm(0, 0, 0, 1, $mon-1, $year);
$mon++; if ($mon == 13) { $mon = 1; $year++ }
my $to_time = Time::Local::timegm(0, 0, 0, 1, $mon-1, $year) - 1;
list_new_cpan_dists(
%args,
from_time => DateTime->from_epoch(epoch => $from_time),
to_time => DateTime->from_epoch(epoch => $to_time),
(exclude_dists => $args{exclude_dists} ) x !!defined($args{exclude_dists}),
(exclude_dists_re => $args{exclude_dists_re} ) x !!defined($args{exclude_dists_re}),
(exclude_authors => $args{exclude_authors} ) x !!defined($args{exclude_authors}),
(exclude_authors_re => $args{exclude_authors_re}) x !!defined($args{exclude_authors_re}),
);
}
sub _htmlize {
require HTML::Entities;
my $res = shift;
my @html;
push @html, "<table>\n";
my $cols = $res->[3]{'table.fields'};
push @html, "<tr>\n";
for my $col (@$cols) {
next if $col =~ /\A(first|latest)_(time)\z/;
push @html, "<th>$col</th>\n";
}
push @html, "</tr>\n\n";
{
no warnings 'uninitialized';
for my $row (@{ $res->[2] }) {
push @html, "<tr>\n";
for my $col (@$cols) {
next if $col =~ /\A(first|latest)_(time)\z/;
my $cell = HTML::Entities::encode_entities($row->{$col});
if ($col eq 'author') {
$cell = qq(<a href="https://metacpan.org/author/$cell">$cell</a>);
} elsif ($col eq 'dist') {
$cell = qq(<a href="https://metacpan.org/release/$row->{dist}">$cell</a>);
}
push @html, "<td>$cell</td>\n";
}
push @html, "</tr>\n";
}
push @html, "</table>\n";
# stats
my $stats = $res->[3]{'func.stats'};
push @html, "<h3>Stats</h3>\n";
push @html, "<p>Number of new CPAN distributions this period: <b>", $stats->{"Number of new CPAN distributions this period"}, "</b></p>\n";
push @html, "<p>Number of authors releasing new CPAN distributions this period: <b>", $stats->{"Number of authors releasing new CPAN distributions this period"}, "</b></p>\n";
push @html, "<p>Authors by number of new CPAN distributions this period: </p>\n";
push @html, "<table>\n";
push @html, "<tr><th>No</th><th>Author</th><th>Distributions</th></tr>\n";
my $i = 1;
for my $rec (@{ $stats->{"Authors by number of new CPAN distributions this period"} }) {
push @html, qq(<tr><td>$i</td><td><a href="https://metacpan.org/author/$rec->{author}">$rec->{author}</a></td><td>$rec->{num_dists}</td></tr>\n);
$i++;
}
push @html, "</table>\n";
}
[200, "OK", join("", @html), {'cmdline.skip_format'=>1}];
}
( run in 0.440 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )