Aion-Annotation
view release on metacpan or search on metacpan
lib/Aion/Annotation.pm view on Meta::CPAN
package Aion::Annotation;
use 5.22.0;
no strict; no warnings; no diagnostics;
use common::sense;
our $VERSION = "0.0.3";
# ÐеÑолÑнÑй пÑÑÑ Ð´Ð»Ñ ÑканиÑованиÑ
use config LIB => ['lib'];
# ÐиÑекÑоÑÐ¸Ñ Ð² коÑоÑÑÑ ÑкладÑваÑÑ ÑÐ°Ð¹Ð»Ñ ÐºÐ¾Ð½ÑигÑÑаÑии
use config INI => 'etc/annotation';
# ÐиÑекÑоÑÐ¸Ñ Ñ ÐºÐµÑем
use config CACHE => 'var/cache';
use Aion::Fs qw/find erase mkpath path mtime from_pkg to_pkg/;
use POSIX qw/strftime/;
use Time::Local qw/timelocal/;
use Aion;
with qw/Aion::Run/;
# ÐÐ¾Ð´Ð¾Ð²Ð°Ñ Ð±Ð°Ð·Ð° Ð´Ð»Ñ ÑканиÑованиÑ
has lib => (is => 'ro', isa => ArrayRef[Str], arg => '-l', default => LIB);
# ÐиÑекÑоÑÐ¸Ñ ÐºÑда ÑоÑ
ÑанÑÑÑ ÑÐ°Ð¹Ð»Ñ Ð°Ð½Ð½Ð¾ÑаÑий
has ini => (is => 'ro', isa => Str, arg => '-i', default => INI);
# ÐÑоÑÑо ÑÑиÑаÑÑ Ð°Ð½Ð½Ð¾ÑаÑии
has force => (is => 'ro', isa => Bool, arg => '-f', default => 0);
# ÐнноÑаÑии: annotation_name.pkg.sub_or_has_name => [[line, annotation_desc]...]
has ann => (is => 'ro', isa => HashRef[HashRef[HashRef[ArrayRef[Tuple[Int, Str]]]]], default => sub {
my $self = shift;
my %ann;
return \%ann if $self->force;
return \%ann if !-d(my $ini = $self->ini);
while(<$ini/*.ann>) {
my $path = $_;
my $annotation_name = path()->{name};
open my $f, "<:utf8", $_ or do { warn "$_ not opened: $!"; next };
while(<$f>) {
warn "$path corrupt on line $.!" unless /^([\w:]+)#(\w*),(\d+)=(.*)$/;
push @{$ann{$annotation_name}{$1}{$2}}, [$3, $4];
}
close $f;
}
\%ann
});
# ÐÑÑÑ Ðº ÑÐ°Ð¹Ð»Ñ Ñ ÐºÐ¾Ð¼Ð¼ÐµÐ½ÑаÑиÑми
has remark_path => (is => 'ro', isa => Str, default => sub { shift->ini . "/remarks.ini" });
# ÐомменÑаÑии: pkg.sub_or_has_name => [[line, remark]...]
has remark => (is => 'ro', isa => HashRef[HashRef[Tuple[Int, ArrayRef[Str]]]], default => sub {
my ($self) = @_;
my %remark;
return \%remark if $self->force;
my $remark_path = $self->remark_path;
return \%remark if !-e $remark_path;
open my $f, "<:utf8", $remark_path or do { warn "$remark_path not opened: $!"; return \%remark };
while(<$f>) {
warn "$remark_path corrupt on line $.!" unless /^([\w:]+)#(\w*),(\d+)=(.*)$/;
$remark{$1}{$2} = [$3, [map { s/\\(.)/$1/gr } split /\\n/, $4]];
}
close $f;
\%remark
});
# ÐÑÑÑ Ðº ÑÐ°Ð¹Ð»Ñ Ñ Ð²Ñеменем поÑледнего доÑÑÑпа к модÑлÑм
has modules_mtime_path => (is => 'ro', isa => Str, default => CACHE . "/modules.mtime.ini");
# ÐÑÐµÐ¼Ñ Ð¿Ð¾Ñледнего доÑÑÑпа к модÑлÑм: pkg => unixtime
has modules_mtime => (is => 'ro', isa => HashRef[Int], default => sub {
my ($self) = @_;
my %mtime;
return \%mtime if $self->force;
my $mtime_path = $self->modules_mtime_path;
return \%mtime if !-e $mtime_path;
open my $f, "<:utf8", $mtime_path or do { warn "$mtime_path not opened: $!"; return 0 };
while(<$f>) {
warn "$mtime_path corrupt on line $.!" unless /^(?<module>[\w:]+)=(?<year>\d{4})-(?<mon>\d{2})-(?<mday>\d{2}) (?<hour>\d{2}):(?<min>\d{2}):(?<sec>\d{2})$/;
$mtime{$+{module}} = timelocal($+{sec}, $+{min}, $+{hour}, $+{mday}, $+{mon} - 1, $+{year});
}
close $f;
\%mtime
});
# СканиÑÑÐµÑ Ð¼Ð¾Ð´Ñли и ÑоÑ
ÑанÑÐµÑ Ð°Ð½Ð½Ð¾ÑаÑии
#@run aion:scan âScan modules and save annotationsâ
sub scan {
my ($self) = @_;
my @libs = @{$self->lib};
s/\/$// for @libs;
my $modules_mtime = $self->modules_mtime;
my $ann = $self->ann;
my $remark = $self->remark;
my %exists;
for my $lib (@libs) {
my $iter = find $lib, "*.pm", "-f";
while(<$iter>) {
my $pkg = to_pkg(substr $_, 1 + length $lib);
$exists{$pkg} = 1;
my $mtime = int mtime;
next if !$self->force && exists $modules_mtime->{$pkg} && $modules_mtime->{$pkg} == $mtime;
$modules_mtime->{$pkg} = $mtime;
delete $_->{$pkg} for values %$ann;
delete $remark->{$pkg};
open my $f, "<:utf8", $_ or do { warn "$_ not opened: $!"; next };
my @ann; my @rem;
my $save_annotation = sub {
my ($name, $pkg1) = @_;
$pkg1 //= $pkg;
push @{$ann->{$_->[0]}{$pkg1}{$name}}, $_->[1] for @ann;
$remark->{$pkg1}{$name} = [$., [@rem]] if @rem;
@ann = @rem = ();
};
while(<$f>) {
last if /^(__END__|__DATA__)\s*$/;
push @ann, [$1, [$., $2]] if /^#\@(\w+)\s+(.*?)\s*$/;
push @rem, $1 if /^#\s(.*?)\s*$/;
$save_annotation->() if /^\s*$/;
$save_annotation->($2, $1) if /^sub\s+(?:([\w:]+)::)?(\w+)/;
$save_annotation->($+{s}) if /^has \s+ (?: (?<s>\w+) | '(?<s>(\\'|[^'])*)' | "(?<s>(\\"|[^"])*)" )/x;
}
$save_annotation->();
close $f;
}
}
# УдалÑем пакеÑÑ Ð² анноÑаÑиÑÑ
, коÑоÑÑÑ
Ñже Ð½ÐµÑ Ð² пÑоекÑе
for my $annotation_name (keys %$ann) {
my $pkgs = $ann->{$annotation_name};
for my $pkg (keys %$pkgs) {
delete $pkgs->{$pkg} if !exists $exists{$pkg};
}
}
mkpath($self->ini . "/");
# СоÑ
ÑанÑм анноÑаÑии и ÑдалÑем ÑÐ°Ð¹Ð»Ñ Ñ Ð°Ð½Ð½Ð¾ÑаÑиÑми, коÑоÑÑÑ
Ñже Ð½ÐµÑ Ð² пÑоекÑе
for my $annotation_name (sort keys %$ann) {
my $pkgs = $ann->{$annotation_name};
my $path = $self->annotation_path($annotation_name);
if(!keys %$pkgs) {
erase($path) if -e $path;
next;
}
open my $f, ">:utf8", $path or do { warn "$path not writed: $!" };
for my $pkg (sort keys %$pkgs) {
my $subs = $pkgs->{$pkg};
for my $sub (sort keys %$subs) {
my $annotation = $subs->{$sub};
print $f "$pkg#$sub,$_->[0]=$_->[1]\n" for @$annotation;
}
}
close $f;
}
# УдалÑем вÑеменна Ñайлов, коÑоÑÑÑ
Ñже Ð½ÐµÑ Ð² пÑоекÑе
for my $pkg (keys %$modules_mtime) {
delete $modules_mtime->{$pkg} if !exists $exists{$pkg};
}
# СоÑ
ÑанÑем вÑÐµÐ¼Ñ Ð¿Ð¾Ñледнего Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñайлов
my $mtime_path = mkpath $self->modules_mtime_path;
open my $f, ">:utf8", $mtime_path or do { warn "$mtime_path not writed: $!" };
printf $f "%s=%s\n", $_, strftime('%Y-%m-%d %H:%M:%S', localtime $modules_mtime->{$_}) for sort grep { $modules_mtime->{$_} } keys %$modules_mtime;
close $f;
# СоÑ
ÑанÑем комменÑаÑии
my $remark_path = $self->remark_path;
open my $f, ">:utf8", $remark_path or do { warn "$remark_path not writed: $!" };
for my $pkg (sort keys %$remark) {
next if !exists $exists{$pkg};
my $subs = $remark->{$pkg};
for my $sub (sort keys %$subs) {
my ($line, $rem) = @{$subs->{$sub}};
print $f "$pkg#$sub,$line=", join("\\n", @$rem), "\n" if @$rem;
}
}
close $f;
$self
}
# ÐÑÑÑ Ðº ÑÐ°Ð¹Ð»Ñ Ð°Ð½Ð½Ð¾ÑаÑий
sub annotation_path {
my ($self, $annotation_name) = @_;
return $self->ini . "/$annotation_name.ann";
};
1;
__END__
=encoding utf-8
=head1 NAME
Aion::Annotation - processes annotations in perl modules
=head1 VERSION
0.0.3
=head1 SYNOPSIS
lib/For/Test.pm file:
package For::Test;
# The package for testing
#@deprecated for_test
#@deprecated
#@todo add1
# Is property
# readonly
has abc => (is => 'ro');
#@todo add2
#@param Int $a
#@param Int[] $r
sub xyz {}
1;
use Aion::Annotation;
Aion::Annotation->new->scan;
( run in 0.511 second using v1.01-cache-2.11-cpan-39bf76dae61 )