1# Copyright © 2009 Raphaël Hertzog <hertzog@debian.org>
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16package Dpkg::Changelog::Entry;
17
18use strict;
19use warnings;
20
21our $VERSION = '1.01';
22
23use Carp;
24
25use Dpkg::Gettext;
26use Dpkg::ErrorHandling;
27use Dpkg::Control::Changelog;
28
29use overload
30    '""' => \&output,
31    'eq' => sub { defined($_[1]) and "$_[0]" eq "$_[1]" },
32    fallback => 1;
33
34=encoding utf8
35
36=head1 NAME
37
38Dpkg::Changelog::Entry - represents a changelog entry
39
40=head1 DESCRIPTION
41
42This object represents a changelog entry. It is composed
43of a set of lines with specific purpose: an header line, changes lines, a
44trailer line. Blank lines can be between those kind of lines.
45
46=head1 METHODS
47
48=over 4
49
50=item $entry = Dpkg::Changelog::Entry->new()
51
52Creates a new object. It doesn't represent a real changelog entry
53until one has been successfully parsed or built from scratch.
54
55=cut
56
57sub new {
58    my $this = shift;
59    my $class = ref($this) || $this;
60
61    my $self = {
62	header => undef,
63	changes => [],
64	trailer => undef,
65	blank_after_header => [],
66	blank_after_changes => [],
67	blank_after_trailer => [],
68    };
69    bless $self, $class;
70    return $self;
71}
72
73=item $str = $entry->output()
74
75=item "$entry"
76
77Get a string representation of the changelog entry.
78
79=item $entry->output($fh)
80
81Print the string representation of the changelog entry to a
82filehandle.
83
84=cut
85
86sub _format_output_block {
87    my $lines = shift;
88    return join('', map { $_ . "\n" } @{$lines});
89}
90
91sub output {
92    my ($self, $fh) = @_;
93    my $str = '';
94    $str .= $self->{header} . "\n" if defined($self->{header});
95    $str .= _format_output_block($self->{blank_after_header});
96    $str .= _format_output_block($self->{changes});
97    $str .= _format_output_block($self->{blank_after_changes});
98    $str .= $self->{trailer} . "\n" if defined($self->{trailer});
99    $str .= _format_output_block($self->{blank_after_trailer});
100    print { $fh } $str if defined $fh;
101    return $str;
102}
103
104=item $entry->get_part($part)
105
106Return either a string (for a single line) or an array ref (for multiple
107lines) corresponding to the requested part. $part can be
108"header, "changes", "trailer", "blank_after_header",
109"blank_after_changes", "blank_after_trailer".
110
111=cut
112
113sub get_part {
114    my ($self, $part) = @_;
115    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
116    return $self->{$part};
117}
118
119=item $entry->set_part($part, $value)
120
121Set the value of the corresponding part. $value can be a string
122or an array ref.
123
124=cut
125
126sub set_part {
127    my ($self, $part, $value) = @_;
128    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
129    if (ref($self->{$part})) {
130	if (ref($value)) {
131	    $self->{$part} = $value;
132	} else {
133	    $self->{$part} = [ $value ];
134	}
135    } else {
136	$self->{$part} = $value;
137    }
138}
139
140=item $entry->extend_part($part, $value)
141
142Concatenate $value at the end of the part. If the part is already a
143multi-line value, $value is added as a new line otherwise it's
144concatenated at the end of the current line.
145
146=cut
147
148sub extend_part {
149    my ($self, $part, $value, @rest) = @_;
150    croak "invalid part of changelog entry: $part" unless exists $self->{$part};
151    if (ref($self->{$part})) {
152	if (ref($value)) {
153	    push @{$self->{$part}}, @$value;
154	} else {
155	    push @{$self->{$part}}, $value;
156	}
157    } else {
158	if (defined($self->{$part})) {
159	    if (ref($value)) {
160		$self->{$part} = [ $self->{$part}, @$value ];
161	    } else {
162		$self->{$part} .= $value;
163	    }
164	} else {
165	    $self->{$part} = $value;
166	}
167    }
168}
169
170=item $is_empty = $entry->is_empty()
171
172Returns 1 if the changelog entry doesn't contain anything at all.
173Returns 0 as soon as it contains something in any of its non-blank
174parts.
175
176=cut
177
178sub is_empty {
179    my $self = shift;
180    return !(defined($self->{header}) || defined($self->{trailer}) ||
181	     scalar(@{$self->{changes}}));
182}
183
184=item $entry->normalize()
185
186Normalize the content. Strip whitespaces at end of lines, use a single
187empty line to separate each part.
188
189=cut
190
191sub normalize {
192    my $self = shift;
193    if (defined($self->{header})) {
194	$self->{header} =~ s/\s+$//g;
195	$self->{blank_after_header} = [''];
196    } else {
197	$self->{blank_after_header} = [];
198    }
199    if (scalar(@{$self->{changes}})) {
200	s/\s+$//g foreach @{$self->{changes}};
201	$self->{blank_after_changes} = [''];
202    } else {
203	$self->{blank_after_changes} = [];
204    }
205    if (defined($self->{trailer})) {
206	$self->{trailer} =~ s/\s+$//g;
207	$self->{blank_after_trailer} = [''];
208    } else {
209	$self->{blank_after_trailer} = [];
210    }
211}
212
213=item $src = $entry->get_source()
214
215Return the name of the source package associated to the changelog entry.
216
217=cut
218
219sub get_source {
220    return;
221}
222
223=item $ver = $entry->get_version()
224
225Return the version associated to the changelog entry.
226
227=cut
228
229sub get_version {
230    return;
231}
232
233=item @dists = $entry->get_distributions()
234
235Return a list of target distributions for this version.
236
237=cut
238
239sub get_distributions {
240    return;
241}
242
243=item $fields = $entry->get_optional_fields()
244
245Return a set of optional fields exposed by the changelog entry.
246It always returns a Dpkg::Control object (possibly empty though).
247
248=cut
249
250sub get_optional_fields {
251    return Dpkg::Control::Changelog->new();
252}
253
254=item $urgency = $entry->get_urgency()
255
256Return the urgency of the associated upload.
257
258=cut
259
260sub get_urgency {
261    return;
262}
263
264=item $maint = $entry->get_maintainer()
265
266Return the string identifying the person who signed this changelog entry.
267
268=cut
269
270sub get_maintainer {
271    return;
272}
273
274=item $time = $entry->get_timestamp()
275
276Return the timestamp of the changelog entry.
277
278=cut
279
280sub get_timestamp {
281    return;
282}
283
284=item $time = $entry->get_timepiece()
285
286Return the timestamp of the changelog entry as a Time::Piece object.
287
288This function might return undef if there was no timestamp.
289
290=cut
291
292sub get_timepiece {
293    return;
294}
295
296=item $str = $entry->get_dpkg_changes()
297
298Returns a string that is suitable for usage in a C<Changes> field
299in the output format of C<dpkg-parsechangelog>.
300
301=cut
302
303sub get_dpkg_changes {
304    my $self = shift;
305    my $header = $self->get_part('header') // '';
306    $header =~ s/\s+$//;
307    return "\n$header\n\n" . join("\n", @{$self->get_part('changes')});
308}
309
310=back
311
312=head1 CHANGES
313
314=head2 Version 1.01 (dpkg 1.18.8)
315
316New method: $entry->get_timepiece().
317
318=head2 Version 1.00 (dpkg 1.15.6)
319
320Mark the module as public.
321
322=cut
323
3241;
325