summaryrefslogtreecommitdiffstats
path: root/lib/Lintian/Output/HTML.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Lintian/Output/HTML.pm')
-rw-r--r--lib/Lintian/Output/HTML.pm331
1 files changed, 331 insertions, 0 deletions
diff --git a/lib/Lintian/Output/HTML.pm b/lib/Lintian/Output/HTML.pm
new file mode 100644
index 0000000..8fd1126
--- /dev/null
+++ b/lib/Lintian/Output/HTML.pm
@@ -0,0 +1,331 @@
+# Copyright (C) 2020-2021 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+package Lintian::Output::HTML;
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use Path::Tiny;
+use Text::Markdown::Discount qw(markdown);
+use Text::Xslate qw(mark_raw);
+use Time::Duration;
+use Time::Moment;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Lintian::Output::Markdown qw(markdown_citation);
+
+const my $EMPTY => q{};
+const my $SPACE => q{ };
+const my $NEWLINE => qq{\n};
+const my $PARAGRAPH_BREAK => $NEWLINE x 2;
+
+const my %CODE_PRIORITY => (
+ 'E' => 30,
+ 'W' => 40,
+ 'I' => 50,
+ 'P' => 60,
+ 'X' => 70,
+ 'C' => 80,
+ 'O' => 90,
+ 'M' => 100,
+);
+
+use Moo;
+use namespace::clean;
+
+with 'Lintian::Output::Grammar';
+
+=head1 NAME
+
+Lintian::Output::HTML - standalone HTML hint output
+
+=head1 SYNOPSIS
+
+ use Lintian::Output::HTML;
+
+=head1 DESCRIPTION
+
+Provides standalone HTML hint output.
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item issue_hints
+
+Print all hints passed in array. A separate arguments with processables
+is necessary to report in case no hints were found.
+
+=cut
+
+sub issue_hints {
+ my ($self, $profile, $groups) = @_;
+
+ $groups //= [];
+
+ my %output;
+
+ my $lintian_version = $ENV{LINTIAN_VERSION};
+ $output{'lintian-version'} = $lintian_version;
+
+ my @allgroups_output;
+ $output{groups} = \@allgroups_output;
+
+ for my $group (sort { $a->name cmp $b->name } @{$groups}) {
+
+ my %group_output;
+
+ $group_output{'group-id'} = $group->name;
+ $group_output{name} = $group->source_name;
+ $group_output{version} = $group->source_version;
+
+ my $start = Time::Moment->from_string($group->processing_start);
+ my $end = Time::Moment->from_string($group->processing_end);
+ $group_output{start} = $start->strftime('%c');
+ $group_output{end} = $end->strftime('%c');
+ $group_output{duration} = duration($start->delta_seconds($end));
+
+ my @processables = $group->get_processables;
+ my $any_processable = shift @processables;
+ $group_output{'maintainer'}
+ = $any_processable->fields->value('Maintainer');
+
+ push(@allgroups_output, \%group_output);
+
+ my @allfiles_output;
+ $group_output{'input-files'} = \@allfiles_output;
+
+ for my $processable (sort {$a->path cmp $b->path}
+ $group->get_processables) {
+ my %file_output;
+ $file_output{filename} = path($processable->path)->basename;
+ $file_output{hints}
+ = $self->hintlist($profile, $processable->hints);
+ push(@allfiles_output, \%file_output);
+ }
+ }
+
+ my $style_sheet = $profile->data->style_sheet->css;
+
+ my $templatedir = "$ENV{LINTIAN_BASE}/templates";
+ my $tx = Text::Xslate->new(path => [$templatedir]);
+ my $page = $tx->render(
+ 'standalone-html.tx',
+ {
+ title => 'Lintian Tags',
+ style_sheet => mark_raw($style_sheet),
+ output => \%output,
+ }
+ );
+
+ print encode_utf8($page);
+
+ return;
+}
+
+=item C<hintlist>
+
+=cut
+
+sub hintlist {
+ my ($self, $profile, $arrayref) = @_;
+
+ my %sorter;
+ for my $hint (@{$arrayref // []}) {
+
+ my $tag = $profile->get_tag($hint->tag_name);
+
+ my $override_status = 0;
+ $override_status = 1
+ if defined $hint->override || @{$hint->masks};
+
+ my $ranking_code = $tag->code;
+ $ranking_code = 'X'
+ if $tag->experimental;
+ $ranking_code = 'O'
+ if defined $hint->override;
+ $ranking_code = 'M'
+ if @{$hint->masks};
+
+ my $code_priority = $CODE_PRIORITY{$ranking_code};
+
+ push(
+ @{
+ $sorter{$override_status}{$code_priority}{$tag->name}
+ {$hint->context}
+ },
+ $hint
+ );
+ }
+
+ my @sorted;
+ for my $override_status (sort keys %sorter) {
+ my %by_code_priority = %{$sorter{$override_status}};
+
+ for my $code_priority (sort { $a <=> $b } keys %by_code_priority) {
+ my %by_tag_name = %{$by_code_priority{$code_priority}};
+
+ for my $tag_name (sort keys %by_tag_name) {
+ my %by_context = %{$by_tag_name{$tag_name}};
+
+ for my $context (sort keys %by_context) {
+
+ my $hints
+ = $sorter{$override_status}{$code_priority}{$tag_name}
+ {$context};
+
+ push(@sorted, $_)for @{$hints};
+ }
+ }
+ }
+ }
+
+ my @html_hints;
+ for my $hint (@sorted) {
+
+ my $tag = $profile->get_tag($hint->tag_name);
+
+ my %html_hint;
+ push(@html_hints, \%html_hint);
+
+ $html_hint{tag_name} = $hint->tag_name;
+
+ $html_hint{url} = 'https://lintian.debian.org/tags/' . $hint->tag_name;
+
+ $html_hint{context} = $hint->context
+ if length $hint->context;
+
+ $html_hint{visibility} = $tag->visibility;
+
+ $html_hint{visibility} = 'experimental'
+ if $tag->experimental;
+
+ my @comments;
+ if ($hint->override) {
+
+ $html_hint{visibility} = 'override';
+
+ push(@comments, $hint->override->justification)
+ if length $hint->override->justification;
+ }
+
+ # order matters
+ $html_hint{visibility} = 'mask'
+ if @{ $hint->masks };
+
+ for my $mask (@{$hint->masks}) {
+
+ push(@comments, 'masked by screen ' . $mask->screen);
+ push(@comments, $mask->excuse)
+ if length $mask->excuse;
+ }
+
+ $html_hint{comments} = \@comments
+ if @comments;
+ }
+
+ return \@html_hints;
+}
+
+=item describe_tags
+
+=cut
+
+sub describe_tags {
+ my ($self, $data, $tags) = @_;
+
+ for my $tag (@{$tags}) {
+
+ say encode_utf8('<p>Name: ' . $tag->name . '</p>');
+ say encode_utf8($EMPTY);
+
+ print encode_utf8(markdown($self->markdown_description($data, $tag)));
+ }
+
+ return;
+}
+
+=item markdown_description
+
+=cut
+
+sub markdown_description {
+ my ($self, $data, $tag) = @_;
+
+ my $description = $tag->explanation;
+
+ my @extras;
+
+ if (@{$tag->see_also}) {
+
+ my @markdown
+ = map { markdown_citation($data, $_) } @{$tag->see_also};
+ my $references
+ = 'Please refer to '
+ . $self->oxford_enumeration('and', @markdown)
+ . ' for details.';
+
+ push(@extras, $references);
+ }
+
+ push(@extras, 'Visibility: '. $tag->visibility);
+
+ push(@extras, 'Check: ' . $tag->check)
+ if length $tag->check;
+
+ push(@extras, 'Renamed from: ' . join($SPACE, @{$tag->renamed_from}))
+ if @{$tag->renamed_from};
+
+ push(@extras, 'This tag is experimental.')
+ if $tag->experimental;
+
+ push(@extras,
+ 'This tag is a classification. There is no issue in your package.')
+ if $tag->visibility eq 'classification';
+
+ for my $screen (@{$tag->screens}) {
+
+ my $screen_description = 'Screen: ' . $screen->name . $NEWLINE;
+ $screen_description
+ .= 'Advocates: ' . join(', ', @{$screen->advocates}) . $NEWLINE;
+ $screen_description .= 'Reason: ' . $screen->reason . $NEWLINE;
+
+ $screen_description .= 'See-Also: ' . $NEWLINE;
+
+ push(@extras, $screen_description);
+ }
+
+ $description .= $PARAGRAPH_BREAK . $_ for @extras;
+
+ return $description;
+}
+
+=back
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et