NAME
Aion::Annotation::Reader - reads annotations, comments and module update times
VERSION
0.1.0
SYNOPSIS
File etc/annotation/todo.ann:
For::Test#abc,5=add1
For::Test#xyz,11=add2
File etc/annotation/remarks.ini:
For::Test#,4=The package for testing
For::Test#abc,9=Is property\n readonly
File var/cache/modules.mtime.ini:
For::Test=2025-01-02 03:04:05
use Aion::Annotation::Reader;
use Time::Local qw/timelocal/;
my $reader = Aion::Annotation::Reader->new('todo');
my @ann; push @ann, $_ while <$reader>;
my $ann = [
{pkg => 'For::Test', name => 'abc', line => '5', annotation => 'add1'},
{pkg => 'For::Test', name => 'xyz', line => '11', annotation => 'add2'},
];
\@ann # --> $ann
my $reader_remarks = Aion::Annotation::Reader->new(Aion::Annotation::Reader::READ_REMARKS);
my $remarks = [
{pkg => 'For::Test', name => '', line => '4', remark => 'The package for testing'},
{pkg => 'For::Test', name => 'abc', line => '9', remark => "Is property\n readonly"},
];
\@{$reader_remarks} # --> $remarks
my $reader_mtime = Aion::Annotation::Reader->new(Aion::Annotation::Reader::READ_MTIME);
# Время хранится как unixtime, поэтому ожидаемое значение не зависит от часового пояса
my $mtime = [{pkg => 'For::Test', mtime => timelocal(5, 4, 3, 2, 0, 2025)}];
\@{$reader_mtime} # --> $mtime
DESCRIPTION
Aion::Annotation::Reader reads the files that Aion::Annotation creates and returns their contents line by line as hashes.
Three types of data are available:
- 1. Annotations from files name.ann (the name is passed to the constructor).
- 2. Comments from remarks.ini.
- 3. Module update times from modules.mtime.ini.
The reader is designed as an iterator on top of a file: the constructor opens the file, and the next method returns the next recognized element. The object overloads a number of operations so that the reader can be used as a file or function directly in expressions.
Recognized strings are represented by hashes:
- 1. Annotation (file etc/annotation/*.ann) – keys
pkg,name,line,annotation. - 2. Comment (file etc/annotation/remarks.ini) – keys
pkg,name,line,remark. - 3. Time (file var/cache/modules.mtime.ini) – keys
pkg,mtime.
Directories are configured by the environment variables AION_ANNOTATION_INI (default etc/annotation) and AION_ANNOTATION_CACHE (default var/cache).
OVERLOAD
Aion::Annotation::Reader overloads the operations:
<>
The reader's challenge.
my $reader = Aion::Annotation::Reader->new('todo');
my @ann; push @ann, $_ while <$reader>;
0+@ann # -> 2
@{}
List of all elements.
my $reader = Aion::Annotation::Reader->new('todo');
my $ann = [
{pkg => 'For::Test', name => 'abc', line => '5', annotation => 'add1'},
{pkg => 'For::Test', name => 'xyz', line => '11', annotation => 'add2'},
];
\@{$reader} # --> $ann
&{}
Call as functions.
Each call returns the following element or undef at the end:
my $reader = Aion::Annotation::Reader->new('todo');
&$reader # --> {pkg => 'For::Test', name => 'abc', line => '5', annotation => 'add1'}
&$reader # --> {pkg => 'For::Test', name => 'xyz', line => '11', annotation => 'add2'}
&$reader # -> undef
*{}
File descriptor.
my $reader = Aion::Annotation::Reader->new('todo');
my $fh = *$reader;
readline $fh # ~> ^For::Test#abc,5=add1$
-X
File operations.
Operations like -e, -s, -f are performed on the reader file. The result of the operation is cached:
my $reader = Aion::Annotation::Reader->new('todo');
-e $reader # -> 1
-s $reader # -> 43
-f $reader # -> 1
""
String representation.
my $reader = Aion::Annotation::Reader->new('todo');
"$reader" # => Aion::Annotation::Reader<ann,etc/annotation/todo.ann>
CONSTANTS
READ_REMARKS
Special name with which the comment file is read:
Aion::Annotation::Reader::READ_REMARKS # -> "-remarks"
READ_MTIME
Special name with which the update times file is read:
Aion::Annotation::Reader::READ_MTIME # -> "-mtime"
MODULES_MTIME_FILE
Update times file name:
Aion::Annotation::Reader::MODULES_MTIME_FILE # -> "modules.mtime.ini"
REMARKS_FILE
Comment file name:
Aion::Annotation::Reader::REMARKS_FILE # -> "remarks.ini"
LINE_REGEX
Annotation or comment string regular expression:
my $line = 'For::Test#abc,5=add1';
my ($pkg, $name, $lineno, $text) = $line =~ Aion::Annotation::Reader::LINE_REGEX;
"$pkg|$name|$lineno|$text" # -> "For::Test|abc|5|add1"
MTIME_REGEX
Update time string regular expression:
my $line = 'For::Test=2025-01-02 03:04:05';
$line =~ Aion::Annotation::Reader::MTIME_REGEX;
"$+{module}|$+{year}-$+{mon}-$+{mday} $+{hour}:$+{min}:$+{sec}" # -> "For::Test|2025-01-02 03:04:05"
SUBROUTINES
new ($cls, $ann)
Constructor. $ann is passed the name of the annotation (then reads etc/annotation/$ann.ann), or the constants READ_REMARKS or READ_MTIME. The file opens immediately in the constructor:
my $reader = Aion::Annotation::Reader->new('todo');
ref $reader # -> "Aion::Annotation::Reader"
$reader->{type} # -> "ann"
$reader->path # -> "etc/annotation/todo.ann"
The call is also valid on an instance:
my $reader = Aion::Annotation::Reader->new('todo');
$reader->new('todo')->path # -> "etc/annotation/todo.ann"
If the file does not exist, an exception is thrown:
eval { Aion::Annotation::Reader->new('no_such_annotation') };
$@ # ~> ^etc/annotation/no_such_annotation\.ann:
path ()
Path to open file:
my $reader = Aion::Annotation::Reader->new(Aion::Annotation::Reader::READ_REMARKS);
$reader->path # -> "etc/annotation/remarks.ini"
next ()
Returns the next element recognized. In a scalar context - one element or undef, in a list context - all remaining elements:
my $reader = Aion::Annotation::Reader->new('todo');
scalar $reader->next # --> {pkg => 'For::Test', name => 'abc', line => '5', annotation => 'add1'}
scalar $reader->next # --> {pkg => 'For::Test', name => 'xyz', line => '11', annotation => 'add2'}
scalar $reader->next # -> undef
DESTROY ()
Closes a file when an object is destroyed. Usually called automatically:
my $f;
{
my $reader = Aion::Annotation::Reader->new('todo');
$f = $reader->{f};
defined fileno $f # -> 1
} # $reader->DESTROY closes the file
fileno $f # -> undef
detect_annotation ($self, $line)
Recognizes the annotation string. Returns a hash with the keys pkg, name, line, annotation:
my $test = {
pkg => 'For::Test', name => 'xyz', line => '11', annotation => 'add2'
};
my $reader = Aion::Annotation::Reader->new('todo');
Aion::Annotation::Reader::detect_annotation($reader, "For::Test#xyz,11=add2\n") # --> $test
detect_remark ($self, $line)
Recognizes a comment line. Escaped sequences \n are turned into newlines, and \<character> into the character itself:
my $test = {
pkg => 'For::Test', name => 'abc', line => '9', remark => "Is property\n readonly"
};
my $reader = Aion::Annotation::Reader->new('todo');
Aion::Annotation::Reader::detect_remark($reader, "For::Test#abc,9=Is property\\n readonly\n") # --> $test
detect_mtime ($self, $line)
Recognizes the update time string. The mtime field is unixtime (in the local time zone):
use Time::Local qw/timelocal/;
my $test = {
pkg => 'For::Test', mtime => timelocal(5, 4, 3, 2, 0, 2025)
};
my $reader = Aion::Annotation::Reader->new('todo');
Aion::Annotation::Reader::detect_mtime($reader, "For::Test=2025-01-02 03:04:05\n") # --> $test
CORRUPT LINES
If the string does not match the format, a warn is issued, but the element is still returned - with undefined fields. This allows you not to interrupt reading on a damaged file:
use Aion::Annotation::Reader;
my @warn;
local $SIG{__WARN__} = sub { push @warn, @_ };
my $reader = Aion::Annotation::Reader->new('todo');
Aion::Annotation::Reader::detect_annotation($reader, "corrupt line\n") # --> {pkg => undef, name => undef, line => undef, annotation => undef}
Aion::Annotation::Reader::detect_remark($reader, "corrupt line\n") # --> {pkg => undef, name => undef, line => undef, remark => ''}
Aion::Annotation::Reader::detect_mtime($reader, "corrupt line\n") # --> {pkg => undef, mtime => undef}
0+@warn # -> 3
$warn[0] # ~> ^etc/annotation/todo\.ann corrupt on line
AUTHOR
Yaroslav O. Kosmina mailto:dart@cpan.org
LICENSE
⚖ GPLv3
COPYRIGHT
The Aion::Annotation::Reader module is copyright © 2026 Yaroslav O. Kosmina. Rusland. All rights reserved.