# -*- perl -*- Lintian::Processable::Overrides # # Copyright © 2019-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, see . package Lintian::Processable::Overrides; use v5.20; use warnings; use utf8; use Const::Fast; use List::SomeUtils qw(none true); use Lintian::Pointer::Item; use Moo::Role; use namespace::clean; const my $EMPTY => q{}; const my $SPACE => q{ }; const my $ASTERISK => q{*}; const my $DOT => q{.}; =head1 NAME Lintian::Processable::Overrides - access to override data =head1 SYNOPSIS use Lintian::Processable; =head1 DESCRIPTION Lintian::Processable::Overrides provides an interface to overrides. =head1 INSTANCE METHODS =over 4 =item parse_overrides =cut sub parse_overrides { my ($self, $contents) = @_; $contents //= $EMPTY; my %override_data; my @comments; my %previous; my @lines = split(/\n/, $contents); my $position = 1; for my $line (@lines) { my $pointer = Lintian::Pointer::Item->new; $pointer->item($self->override_file); $pointer->position($position); my $remaining = $line; # trim both ends $remaining =~ s/^\s+|\s+$//g; if ($remaining eq $EMPTY) { # Throw away comments, as they are not attached to a tag # also throw away the option of "carrying over" the last # comment @comments = (); %previous = (); next; } if ($remaining =~ /^#/) { $remaining =~ s/^# ?//; push(@comments, $remaining); next; } # reduce white space $remaining =~ s/\s+/ /g; # [[pkg-name] [arch-list] [pkg-type]:] [context] my $require_colon = 0; my @architectures; # strip package name, if present; require name # parsing overrides is ambiguous (see #699628) my $package = $self->name; if ($remaining =~ s/^\Q$package\E(?=\s|:)//) { # both spaces or colon were unmatched lookhead $remaining =~ s/^\s+//; $require_colon = 1; } # remove architecture list if ($remaining =~ s/^\[([^\]]*)\](?=\s|:)//) { @architectures = split($SPACE, $1); # both spaces or colon were unmatched lookhead $remaining =~ s/^\s+//; $require_colon = 1; } # remove package type my $type = $self->type; if ($remaining =~ s/^\Q$type\E(?=\s|:)//) { # both spaces or colon were unmatched lookhead $remaining =~ s/^\s+//; $require_colon = 1; } # require and remove colon when any package details are present if ($require_colon && $remaining !~ s/^\s*:\s*//) { $self->pointed_hint('malformed-override', $pointer, 'Expected a colon'); next; } my $hint = $remaining; if (@architectures && $self->architecture eq 'all') { $self->pointed_hint('malformed-override', $pointer, 'Architecture list for arch:all package'); next; } my @invalid = grep { !$self->profile->architectures->valid_restriction($_) } @architectures; $self->pointed_hint('malformed-override', $pointer, "Unknown architecture wildcard $_") for @invalid; next if @invalid; # count negations my $negations = true { /^!/ } @architectures; # confirm it is either all or none unless ($negations == @architectures || $negations == 0) { $self->pointed_hint('malformed-override', $pointer, 'Inconsistent architecture negation'); next; } # strip negations if present s/^!// for @architectures; # proceed when none specified next if @architectures && ( $negations xor none { $self->profile->architectures->restriction_matches($_, $self->architecture) } @architectures ); my ($tagname, $context) = split($SPACE, $hint, 2); $self->pointed_hint('malformed-override', $pointer, "Cannot parse line: $line") unless length $tagname; $context //= $EMPTY; if (($previous{tag} // $EMPTY) eq $tagname && !scalar @comments){ # There are no new comments, no "empty line" in between and # this tag is the same as the last, so we "carry over" the # comment from the previous override (if any). # # Since L::T::Override is (supposed to be) immutable, the new # override can share the reference with the previous one. push(@comments, @{$previous{comments}}); } my %current; $current{tag} = $tagname; # record line number $current{line} = $position; $current{context} = $context; if ($context =~ m/\*/) { # It is a pattern, pre-compute it my $pattern = $context; my $end = $EMPTY; # Trailing "match anything" (if any) my $pat = $EMPTY; # The rest of the pattern # Split does not help us if $pattern ends with * # so we deal with that now if ($pattern =~ s/\Q*\E+\z//){ $end = $DOT . $ASTERISK; } # Are there any * left (after the above)? if ($pattern =~ m/\Q*\E/) { # this works even if $text starts with a *, since # that is split as $EMPTY, my @pargs = split(m/\Q*\E++/, $pattern); $pat = join($DOT . $ASTERISK, map { quotemeta } @pargs); } else { $pat = $pattern; } $current{pattern} = qr/$pat$end/; } $current{comments} = []; push(@{$current{comments}}, @comments); @comments = (); $override_data{$tagname} //= {}; if (exists $override_data{$tagname}{$context}) { my @same_context = ($override_data{$tagname}{$context}{line}, $current{line}); my $lines = join($SPACE, sort @same_context); $self->pointed_hint('duplicate-override-context', $pointer, $tagname, "(lines $lines)"); next; } $override_data{$tagname}{$context} = \%current; %previous = %current; } continue { $position++; } return \%override_data; } 1; =back =head1 AUTHOR Originally written by Felix Lechner for Lintian. =head1 SEE ALSO lintian(1) =cut # Local Variables: # indent-tabs-mode: nil # cperl-indent-level: 4 # End: # vim: syntax=perl sw=4 sts=4 sr et