745abfc42308d6b746f1c72a864ef0546c91d4f6
[maemian] / lib / Maemian / Output.pm
1 # Copyright © 2008 Frank Lichtenheld <frank@lichtenheld.de>
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, you can find it on the World Wide
15 # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
16 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
17 # MA 02110-1301, USA.
18
19 package Maemian::Output;
20
21 use strict;
22 use warnings;
23
24 use v5.8.0; # for PerlIO
25 use base qw(Class::Accessor Exporter);
26
27 # Force export as soon as possible, since some of the modules we load also
28 # depend on us and the sequencing can cause things not to be exported
29 # otherwise.
30 our (@EXPORT, %EXPORT_TAGS, @EXPORT_OK);
31 BEGIN {
32     @EXPORT = ();
33     %EXPORT_TAGS = ( messages => [qw(msg v_msg warning debug_msg delimiter)],
34                      util => [qw(_global_or_object)]);
35     @EXPORT_OK = (@{$EXPORT_TAGS{messages}},
36                   @{$EXPORT_TAGS{util}},
37                   'string');
38 }
39
40 =head1 NAME
41
42 Lintian::Output - Lintian messaging handling
43
44 =head1 SYNOPSIS
45
46     # non-OO
47     use Lintian::Output qw(:messages)
48
49     $Lintian::Output::GLOBAL->verbose(1);
50
51     msg("Something interesting");
52     v_msg("Something less interesting");
53     debug_msg(3, "Something very specfific");
54
55     # OO
56     use Lintian::Output;
57
58     my $out = new Lintian::Output;
59
60     $out->quiet(1);
61     $out->msg("Something interesting");
62     $out->v_msg("Something less interesting");
63     $out->debug_msg(3, "Something very specfific");
64
65 =head1 DESCRIPTION
66
67 Lintian::Output is used for all interaction between lintian and the user.
68 It is designed to be easily extendable via subclassing.
69
70 To simplify usage in the most common cases, many Lintian::Output methods
71 can be used as class methods and will therefor automatically use the object
72 $Lintian::Output::GLOBAL unless their first argument C<isa('Lintian::Output')>.
73
74 =cut
75
76 # support for ANSI color output via colored()
77 use Term::ANSIColor ();
78 use Maemian::Tag::Info ();
79 use Tags ();
80
81 =head1 ACCESSORS
82
83 The following fields define the behaviours of Lintian::Output.
84
85 =over 4
86
87 =item quiet
88
89 If true, will suppress all messages except for warnings.
90
91 =item verbose
92
93 If true, will enable messages issued with v_msg.
94
95 =item debug
96
97 If set to a positive integer, will enable all debug messages issued with
98 a level lower or equal to its value.
99
100 =item color
101
102 Can take the values "never", "always", "auto" or "html".
103
104 Whether to colorize tags based on their severity.  The default is "never",
105 which never uses color.  "always" will always use color, "auto" will use
106 color only if the output is going to a terminal.
107
108 "html" will output HTML <span> tags with a color style attribute (instead
109 of ANSI color escape sequences).
110
111 =item stdout
112
113 I/O handle to use for output of messages and tags.  Defaults to C<\*STDOUT>.
114
115 =item stderr
116
117 I/O handle to use for warnings.  Defaults to C<\*STDERR>.
118
119 =item showdescription
120
121 Whether to show the description of a tag when printing it.
122
123 =item issuedtags
124
125 Hash containing the names of tags which have been issued.
126
127 =back
128
129 =cut
130
131 Maemian::Output->mk_accessors(qw(verbose debug quiet color colors stdout
132     stderr showdescription issuedtags));
133
134 # for the non-OO interface
135 my %default_colors = ( 'E' => 'red' , 'W' => 'yellow' , 'I' => 'cyan',
136                        'P' => 'green' );
137
138 our $GLOBAL = new Lintian::Output;
139
140 sub new {
141     my ($class, %options) = @_;
142     my $self = { %options };
143
144     bless($self, $class);
145
146     $self->stdout(\*STDOUT);
147     $self->stderr(\*STDERR);
148     $self->colors({%default_colors});
149     $self->issuedtags({});
150
151     return $self;
152 }
153
154 =head1 CLASS/INSTANCE METHODS
155
156 These methods can be used both with and without an object.  If no object
157 is given, they will fall back to the $Lintian::Output::GLOBAL object.
158
159 =over 4
160
161 =item C<msg(@args)>
162
163 Will output the strings given in @args, one per line, each line prefixed
164 with 'N: '.  Will do nothing if quiet is true.
165
166 =item C<v_msg(@args)>
167
168 Will output the strings given in @args, one per line, each line prefixed
169 with 'N: '.  Will do nothing unless verbose is true.
170
171 =item C<debug_msg($level, @args)>
172
173 $level should be a positive integer.
174
175 Will output the strings given in @args, one per line, each line prefixed
176 with 'N: '.  Will do nothing unless debug is set to a positive integer
177 >= $level.
178
179 =cut
180
181 sub msg {
182     my ($self, @args) = _global_or_object(@_);
183
184     return if $self->quiet;
185     $self->_message(@args);
186 }
187
188 sub v_msg {
189     my ($self, @args) = _global_or_object(@_);
190
191     return unless $self->verbose;
192     $self->_message(@args);
193 }
194
195 sub debug_msg {
196     my ($self, $level, @args) = _global_or_object(@_);
197
198     return unless $self->debug && ($self->debug >= $level);
199
200     $self->_message(@args);
201 }
202
203 =item C<warning(@args)>
204
205 Will output the strings given in @args on stderr, one per line, each line
206 prefixed with 'warning: '.
207
208 =cut
209
210 sub warning {
211     my ($self, @args) = _global_or_object(@_);
212
213     return if $self->quiet;
214     $self->_warning(@args);
215 }
216
217 =item C<delimiter()>
218
219 Gives back a string that is usable for separating messages in the output.
220 Note: This does not print anything, it just gives back the string, use
221 with one of the methods above, e.g.
222
223  v_msg('foo', delimiter(), 'bar');
224
225 =cut
226
227 sub delimiter {
228     my ($self) = _global_or_object(@_);
229
230     return $self->_delimiter;
231 }
232
233 =item C<issued_tag($tag_name)>
234
235 Indicate that the named tag has been issued.  Returns a boolean value
236 indicating whether the tag had previously been issued by the object.
237
238 =cut
239
240 sub issued_tag {
241     my ($self, $tag_name) = _global_or_object(@_);
242
243     return $self->issuedtags->{$tag_name}++ ? 1 : 0;
244 }
245
246 =item C<string($lead, @args)>
247
248 TODO: Is this part of the public interface?
249
250 =cut
251
252 sub string {
253     my ($self, $lead, @args) = _global_or_object(@_);
254
255     my $output = '';
256     if (@args) {
257         foreach (@args) {
258             $output .= $lead.': '.$_."\n";
259         }
260     } elsif ($lead) {
261         $output .= $lead.".\n";
262     }
263
264     return $output;
265 }
266
267 =back
268
269 =head1 INSTANCE METHODS FOR CONTEXT-AWARE OUTPUT
270
271 The following methods are designed to be called at specific points
272 during program execution and require very specific arguments.  They
273 can only be called as instance methods.
274
275 =over 4
276
277 =item C<print_tag($pkg_info, $tag_info, $extra)>
278
279 Print a tag.  The first two arguments are hash reference with the information
280 about the package and the tag, $extra is the extra information for the tag
281 (if any) as an array reference.  Called from Tags::tag().
282
283 =cut
284
285 sub print_tag {
286     my ($self, $pkg_info, $tag_info, $information) = @_;
287     $information = ' ' . $information if $information ne '';
288     my $code = Tags::get_tag_code($tag_info);
289     my $tag_color = $self->{colors}{$code};
290     $code = 'X' if exists $tag_info->{experimental};
291     $code = 'O' if $tag_info->{overridden}{override};
292     my $type = '';
293     $type = " $pkg_info->{type}" if $pkg_info->{type} ne 'binary';
294
295     my $tag;
296     if ($self->_do_color) {
297         if ($self->color eq 'html') {
298             my $escaped = $tag_info->{tag};
299             $escaped =~ s/&/&amp;/g;
300             $escaped =~ s/</&lt;/g;
301             $escaped =~ s/>/&gt;/g;
302             $tag .= qq(<span style="color: $tag_color">$escaped</span>)
303         } else {
304             $tag .= Term::ANSIColor::colored($tag_info->{tag}, $tag_color);
305         }
306     } else {
307         $tag .= $tag_info->{tag};
308     }
309
310     $self->_print('', "$code: $pkg_info->{pkg}$type", "$tag$information");
311     if (!$self->issued_tag($tag_info->{tag}) and $self->showdescription) {
312         my $info = Lintian::Tag::Info->new($tag_info->{tag});
313         if ($info) {
314             my $description;
315             if ($self->_do_color && $self->color eq 'html') {
316                 $description = $info->description('html', '   ');
317             } else {
318                 $description = $info->description('text', '   ');
319             }
320             $self->_print('', 'N', '');
321             $self->_print('', 'N', split("\n", $description));
322             $self->_print('', 'N', '');
323         }
324     }
325 }
326
327 =item C<print_start_pkg($pkg_info)>
328
329 Called before lintian starts to handle each package.  The version in
330 Lintian::Output uses v_msg() for output.  Called from Tags::select_pkg().
331
332 =cut
333
334 sub print_start_pkg {
335     my ($self, $pkg_info) = @_;
336
337     $self->v_msg($self->delimiter,
338                  "Processing $pkg_info->{type} package $pkg_info->{pkg} (version $pkg_info->{version}) ...");
339 }
340
341 =item C<print_start_pkg($pkg_info)>
342
343 Called after lintian is finished with a package.  The version in
344 Lintian::Output does nothing.  Called from Tags::select_pkg() and
345 Tags::reset_pkg().
346
347 =cut
348
349 sub print_end_pkg {
350 }
351
352 =back
353
354 =head1 INSTANCE METHODS FOR SUBCLASSING
355
356 The following methods are only intended for subclassing and are
357 only available as instance methods.  The methods mentioned in
358 L<CLASS/INSTANCE METHODS>
359 usually only check whether they should do anything at all (according
360 to the values of quiet, verbose, and debug) and then call one of
361 the following methods to do the actual printing. Allmost all of them
362 finally call _print() to do that.  This convoluted scheme is necessary
363 to be able to use the methods above as class methods and still make
364 the behaviour overridable in subclasses.
365
366 =over 4
367
368 =item C<_message(@args)>
369
370 Called by msg(), v_msg(), and debug_msg() to print the
371 message.
372
373 =cut
374
375 sub _message {
376     my ($self, @args) = @_;
377
378     $self->_print('', 'N', @args);
379 }
380
381 =item C<_warning(@args)>
382
383 Called by warning() to print the warning.
384
385 =cut
386
387 sub _warning {
388     my ($self, @args) = @_;
389
390     $self->_print($self->stderr, 'warning', @args);
391 }
392
393 =item C<_print($stream, $lead, @args)>
394
395 Called by _message(), _warning(), and print_tag() to do
396 the actual printing.
397
398 If you override these three methods, you can change
399 the calling convention for this method to pretty much
400 whatever you want.
401
402 The version in Lintian::Output prints the strings in
403 @args, one per line, each line preceded by $lead to
404 the I/O handle given in $stream.
405
406 =cut
407
408 sub _print {
409     my ($self, $stream, $lead, @args) = @_;
410     $stream ||= $self->stdout;
411
412     my $output = $self->string($lead, @args);
413     print {$stream} $output;
414 }
415
416 =item C<_delimiter()>
417
418 Called by delimiter().
419
420 =cut
421
422 sub _delimiter {
423     return '----';
424 }
425
426 =item C<_do_color()>
427
428 Called by print_tag() to determine whether to produce colored
429 output.
430
431 =cut
432
433 sub _do_color {
434     my ($self) = @_;
435
436     return ($self->color eq 'always' || $self->color eq 'html'
437             || ($self->color eq 'auto'
438                 && -t $self->stdout));
439 }
440
441 =back
442
443 =head1 CLASS METHODS
444
445 =over 4
446
447 =item C<_global_or_object(@args)>
448
449 If $args[0] is a object which satisfies C<isa('Lintian::Output')>
450 returns @args, otherwise returns C<($Lintian::Output::GLOBAL, @_)>.
451
452 =back
453
454 =cut
455
456 sub _global_or_object {
457     if (ref($_[0]) and $_[0]->isa('Lintian::Output')) {
458         return @_;
459     } else {
460         return ($Lintian::Output::GLOBAL, @_);
461     }
462 }
463
464 1;
465 __END__
466
467 =head1 EXPORTS
468
469 Lintian::Output exports nothing by default, but the following export
470 tags are available:
471
472 =over 4
473
474 =item :messages
475
476 Exports all the methods in L<CLASS/INSTANCE METHODS>
477
478 =item :util
479
480 Exports all the methods in L<CLASS METHODS>
481
482 =back
483
484 =head1 AUTHOR
485
486 Originally written by Frank Lichtenheld <djpig@debian.org> for Lintian.
487
488 =head1 SEE ALSO
489
490 lintian(1)
491
492 =cut
493
494 # Local Variables:
495 # indent-tabs-mode: t
496 # cperl-indent-level: 4
497 # End:
498 # vim: syntax=perl sw=4 ts=8 noet shiftround