'@{}'
=>
sub
{
return
$_
[0]->{data} };
sub
new {
my
(
$this
,
%opts
) =
@_
;
my
$class
=
ref
(
$this
) ||
$this
;
my
$self
= {
verbose
=> 1,
parse_errors
=> []
};
bless
$self
,
$class
;
$self
->set_options(
%opts
);
return
$self
;
}
sub
set_options {
my
(
$self
,
%opts
) =
@_
;
$self
->{
$_
} =
$opts
{
$_
}
foreach
keys
%opts
;
}
sub
reset_parse_errors {
my
$self
=
shift
;
$self
->{parse_errors} = [];
}
sub
parse_error {
my
(
$self
,
$file
,
$line_nr
,
$error
,
$line
) =
@_
;
push
@{
$self
->{parse_errors}}, [
$file
,
$line_nr
,
$error
,
$line
];
if
(
$self
->{verbose}) {
if
(
$line
) {
warning(
"%20s(l$line_nr): $error\nLINE: $line"
,
$file
);
}
else
{
warning(
"%20s(l$line_nr): $error"
,
$file
);
}
}
}
sub
get_parse_errors {
my
$self
=
shift
;
if
(
wantarray
) {
return
@{
$self
->{parse_errors}};
}
else
{
my
$res
=
''
;
foreach
my
$e
(@{
$self
->{parse_errors}}) {
if
(
$e
->[3]) {
$res
.= report(REPORT_WARN, g_(
"%s(l%s): %s\nLINE: %s"
),
@$e
);
}
else
{
$res
.= report(REPORT_WARN, g_(
'%s(l%s): %s'
),
@$e
);
}
}
return
$res
;
}
}
sub
set_unparsed_tail {
my
(
$self
,
$tail
) =
@_
;
$self
->{unparsed_tail} =
$tail
;
}
sub
get_unparsed_tail {
my
$self
=
shift
;
return
$self
->{unparsed_tail};
}
sub
_sanitize_range {
my
(
$self
,
$r
) =
@_
;
my
$data
=
$self
->{data};
if
(
defined
(
$r
->{offset}) and not
defined
(
$r
->{count})) {
warning(g_(
"'offset' without 'count' has no effect"
))
if
$self
->{verbose};
delete
$r
->{offset};
}
if
((
defined
(
$r
->{count}) ||
defined
(
$r
->{offset})) &&
(
defined
(
$r
->{from}) ||
defined
(
$r
->{since}) ||
defined
(
$r
->{to}) ||
defined
(
$r
->{
until
})))
{
warning(g_(
"you can't combine 'count' or 'offset' with any other "
.
'range option'
))
if
$self
->{verbose};
delete
$r
->{from};
delete
$r
->{since};
delete
$r
->{to};
delete
$r
->{
until
};
}
if
(
defined
(
$r
->{from}) &&
defined
(
$r
->{since})) {
warning(g_(
"you can only specify one of 'from' and 'since', using "
.
"'since'"
))
if
$self
->{verbose};
delete
$r
->{from};
}
if
(
defined
(
$r
->{to}) &&
defined
(
$r
->{
until
})) {
warning(g_(
"you can only specify one of 'to' and 'until', using "
.
"'until'"
))
if
$self
->{verbose};
delete
$r
->{to};
}
my
(
%versions
,
@versions
);
foreach
my
$entry
(@{
$data
}) {
my
$version
=
$entry
->get_version();
next
unless
defined
$version
;
$versions
{
$version
->as_string()} = 1;
push
@versions
,
$version
->as_string();
}
if
((
defined
(
$r
->{since}) and not
exists
$versions
{
$r
->{since}})) {
warning(g_(
"'%s' option specifies non-existing version '%s'"
),
'since'
,
$r
->{since});
warning(g_(
'use newest entry that is earlier than the one specified'
));
foreach
my
$v
(
@versions
) {
if
(version_compare_relation(
$v
, REL_LT,
$r
->{since})) {
$r
->{since} =
$v
;
last
;
}
}
if
(not
exists
$versions
{
$r
->{since}}) {
warning(g_(
'none found, starting from the oldest entry'
));
delete
$r
->{since};
$r
->{from} =
$versions
[-1];
}
}
if
((
defined
(
$r
->{from}) and not
exists
$versions
{
$r
->{from}})) {
warning(g_(
"'%s' option specifies non-existing version '%s'"
),
'from'
,
$r
->{from});
warning(g_(
'use oldest entry that is later than the one specified'
));
my
$oldest
;
foreach
my
$v
(
@versions
) {
if
(version_compare_relation(
$v
, REL_GT,
$r
->{from})) {
$oldest
=
$v
;
}
}
if
(
defined
(
$oldest
)) {
$r
->{from} =
$oldest
;
}
else
{
warning(g_(
"no such entry found, ignoring '%s' parameter '%s'"
),
'from'
,
$r
->{from});
delete
$r
->{from};
}
}
if
(
defined
(
$r
->{
until
}) and not
exists
$versions
{
$r
->{
until
}}) {
warning(g_(
"'%s' option specifies non-existing version '%s'"
),
'until'
,
$r
->{
until
});
warning(g_(
'use oldest entry that is later than the one specified'
));
my
$oldest
;
foreach
my
$v
(
@versions
) {
if
(version_compare_relation(
$v
, REL_GT,
$r
->{
until
})) {
$oldest
=
$v
;
}
}
if
(
defined
(
$oldest
)) {
$r
->{
until
} =
$oldest
;
}
else
{
warning(g_(
"no such entry found, ignoring '%s' parameter '%s'"
),
'until'
,
$r
->{
until
});
delete
$r
->{
until
};
}
}
if
(
defined
(
$r
->{to}) and not
exists
$versions
{
$r
->{to}}) {
warning(g_(
"'%s' option specifies non-existing version '%s'"
),
'to'
,
$r
->{to});
warning(g_(
'use newest entry that is earlier than the one specified'
));
foreach
my
$v
(
@versions
) {
if
(version_compare_relation(
$v
, REL_LT,
$r
->{to})) {
$r
->{to} =
$v
;
last
;
}
}
if
(not
exists
$versions
{
$r
->{to}}) {
warning(g_(
"no such entry found, ignoring '%s' parameter '%s'"
),
'to'
,
$r
->{to});
delete
$r
->{to};
}
}
if
(
defined
(
$r
->{since}) and
$data
->[0]->get_version() eq
$r
->{since}) {
warning(g_(
"'since' option specifies most recent version '%s', ignoring"
),
$r
->{since});
delete
$r
->{since};
}
if
(
defined
(
$r
->{
until
}) and
$data
->[-1]->get_version() eq
$r
->{
until
}) {
warning(g_(
"'until' option specifies oldest version '%s', ignoring"
),
$r
->{
until
});
delete
$r
->{
until
};
}
}
sub
get_range {
my
(
$self
,
$range
) =
@_
;
$range
//= {};
my
$res
=
$self
->_data_range(
$range
);
return
unless
defined
$res
;
if
(
wantarray
) {
return
reverse
@{
$res
}
if
$range
->{
reverse
};
return
@{
$res
};
}
else
{
return
$res
;
}
}
sub
_is_full_range {
my
(
$self
,
$range
) =
@_
;
return
1
if
$range
->{all};
foreach
my
$delim
(
qw(since until from to count offset)
) {
return
0
if
exists
$range
->{
$delim
};
}
return
1;
}
sub
_data_range {
my
(
$self
,
$range
) =
@_
;
my
$data
=
$self
->{data} or
return
;
return
[
@$data
]
if
$self
->_is_full_range(
$range
);
$self
->_sanitize_range(
$range
);
my
(
$start
,
$end
);
if
(
defined
(
$range
->{count})) {
my
$offset
=
$range
->{offset} // 0;
my
$count
=
$range
->{count};
if
(
$offset
> 0) {
$offset
-= (
$count
< 0);
}
elsif
(
$offset
< 0) {
$offset
=
$#$data
+ (
$count
> 0) +
$offset
;
}
else
{
$offset
=
$#$data
if
$count
< 0;
}
$start
=
$end
=
$offset
;
$start
+=
$count
+1
if
$count
< 0;
$end
+=
$count
-1
if
$count
> 0;
$start
= 0
if
$start
< 0;
return
if
$start
>
$#$data
;
$end
=
$#$data
if
$end
>
$#$data
;
return
if
$end
< 0;
$end
=
$start
if
$end
<
$start
;
return
[ @{
$data
}[
$start
..
$end
] ];
}
my
@result
;
my
$include
= 1;
$include
= 0
if
defined
(
$range
->{to}) or
defined
(
$range
->{
until
});
foreach
my
$entry
(@{
$data
}) {
my
$v
=
$entry
->get_version();
$include
= 1
if
defined
(
$range
->{to}) and
$v
eq
$range
->{to};
last
if
defined
(
$range
->{since}) and
$v
eq
$range
->{since};
push
@result
,
$entry
if
$include
;
$include
= 1
if
defined
(
$range
->{
until
}) and
$v
eq
$range
->{
until
};
last
if
defined
(
$range
->{from}) and
$v
eq
$range
->{from};
}
return
\
@result
if
scalar
(
@result
);
return
;
}
sub
abort_early {
my
$self
=
shift
;
my
$data
=
$self
->{data} or
return
;
my
$r
=
$self
->{range} or
return
;
my
$count
=
$r
->{count} // 0;
my
$offset
=
$r
->{offset} // 0;
return
if
$self
->_is_full_range(
$r
);
return
if
$offset
< 0 or
$count
< 0;
if
(
defined
(
$r
->{count})) {
if
(
$offset
> 0) {
$offset
-= (
$count
< 0);
}
my
$start
=
my
$end
=
$offset
;
$end
+=
$count
-1
if
$count
> 0;
return
$start
< @{
$data
} >
$end
;
}
return
unless
defined
(
$r
->{since}) or
defined
(
$r
->{from});
foreach
my
$entry
(@{
$data
}) {
my
$v
=
$entry
->get_version();
return
1
if
defined
(
$r
->{since}) and
$v
eq
$r
->{since};
return
1
if
defined
(
$r
->{from}) and
$v
eq
$r
->{from};
}
return
;
}
sub
output {
my
(
$self
,
$fh
) =
@_
;
my
$str
=
''
;
foreach
my
$entry
(@{
$self
}) {
my
$text
=
$entry
->output();
print
{
$fh
}
$text
if
defined
$fh
;
$str
.=
$text
if
defined
wantarray
;
}
my
$text
=
$self
->get_unparsed_tail();
if
(
defined
$text
) {
print
{
$fh
}
$text
if
defined
$fh
;
$str
.=
$text
if
defined
wantarray
;
}
return
$str
;
}
our
(
@URGENCIES
,
%URGENCIES
);
BEGIN {
@URGENCIES
=
qw(
low
medium
high
critical
emergency
)
;
my
$i
= 1;
%URGENCIES
=
map
{
$_
=>
$i
++ }
@URGENCIES
;
}
sub
_format_dpkg {
my
(
$self
,
$range
) =
@_
;
my
@data
=
$self
->get_range(
$range
) or
return
;
my
$src
=
shift
@data
;
my
$c
= Dpkg::Control::Changelog->new();
$c
->{Urgency} =
$src
->get_urgency() ||
'unknown'
;
$c
->{Source} =
$src
->get_source() ||
'unknown'
;
$c
->{Version} =
$src
->get_version() //
'unknown'
;
$c
->{Distribution} =
join
' '
,
$src
->get_distributions();
$c
->{Maintainer} =
$src
->get_maintainer() //
''
;
$c
->{Date} =
$src
->get_timestamp() //
''
;
$c
->{Timestamp} =
$src
->get_timepiece &&
$src
->get_timepiece->epoch //
''
;
$c
->{Changes} =
$src
->get_dpkg_changes();
my
$opts
=
$src
->get_optional_fields();
my
%closes
;
foreach
my
$f
(
keys
%{
$opts
}) {
if
(
$f
eq
'Urgency'
) {
}
elsif
(
$f
eq
'Closes'
) {
$closes
{
$_
} = 1
foreach
(
split
(/\s+/,
$opts
->{Closes}));
}
else
{
field_transfer_single(
$opts
,
$c
,
$f
);
}
}
foreach
my
$bin
(
@data
) {
my
$oldurg
=
$c
->{Urgency} //
''
;
my
$oldurgn
=
$URGENCIES
{
$c
->{Urgency}} // -1;
my
$newurg
=
$bin
->get_urgency() //
''
;
my
$newurgn
=
$URGENCIES
{
$newurg
} // -1;
$c
->{Urgency} = (
$newurgn
>
$oldurgn
) ?
$newurg
:
$oldurg
;
$c
->{Changes} .=
"\n"
.
$bin
->get_dpkg_changes();
$opts
=
$bin
->get_optional_fields();
foreach
my
$f
(
keys
%{
$opts
}) {
if
(
$f
eq
'Closes'
) {
$closes
{
$_
} = 1
foreach
(
split
(/\s+/,
$opts
->{Closes}));
}
elsif
(not
exists
$c
->{
$f
}) {
field_transfer_single(
$opts
,
$c
,
$f
);
}
}
}
if
(
scalar
keys
%closes
) {
$c
->{Closes} =
join
' '
,
sort
{
$a
<=>
$b
}
keys
%closes
;
}
run_vendor_hook(
'post-process-changelog-entry'
,
$c
);
return
$c
;
}
sub
_format_rfc822 {
my
(
$self
,
$range
) =
@_
;
my
@data
=
$self
->get_range(
$range
) or
return
;
my
@ctrl
;
foreach
my
$entry
(
@data
) {
my
$c
= Dpkg::Control::Changelog->new();
$c
->{Urgency} =
$entry
->get_urgency() ||
'unknown'
;
$c
->{Source} =
$entry
->get_source() ||
'unknown'
;
$c
->{Version} =
$entry
->get_version() //
'unknown'
;
$c
->{Distribution} =
join
' '
,
$entry
->get_distributions();
$c
->{Maintainer} =
$entry
->get_maintainer() //
''
;
$c
->{Date} =
$entry
->get_timestamp() //
''
;
$c
->{Timestamp} =
$entry
->get_timepiece &&
$entry
->get_timepiece->epoch //
''
;
$c
->{Changes} =
$entry
->get_dpkg_changes();
my
$opts
=
$entry
->get_optional_fields();
foreach
my
$f
(
keys
%{
$opts
}) {
field_transfer_single(
$opts
,
$c
,
$f
)
unless
exists
$c
->{
$f
};
}
run_vendor_hook(
'post-process-changelog-entry'
,
$c
);
push
@ctrl
,
$c
;
}
return
@ctrl
;
}
sub
format_range {
my
(
$self
,
$format
,
$range
) =
@_
;
my
@ctrl
;
if
(
$format
eq
'dpkg'
) {
@ctrl
=
$self
->_format_dpkg(
$range
);
}
elsif
(
$format
eq
'rfc822'
) {
@ctrl
=
$self
->_format_rfc822(
$range
);
}
else
{
croak
"unknown changelog output format $format"
;
}
if
(
wantarray
) {
return
@ctrl
;
}
else
{
my
$index
= Dpkg::Index->new(
type
=> CTRL_CHANGELOG);
foreach
my
$c
(
@ctrl
) {
$index
->add(
$c
);
}
return
$index
;
}
}
1;