# repoman: Checks # Copyright 2007 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Id$ import time import re import os from repoman.errors import COPYRIGHT_ERROR, LICENSE_ERROR, CVS_HEADER_ERROR, \ LEADING_SPACES_ERROR, READONLY_ASSIGNMENT_ERROR, TRAILING_WHITESPACE_ERROR, \ MISSING_QUOTES_ERROR class ContentCheckException(Exception): """Parent class for exceptions relating to invalid content""" def __init__(self, str): Exception.__init__(self, str) class ContentCheck(object): """Given a file-like object, run checks over it's content Args: contents - A file-like object, preferably a StringIO instance Raises: ContentCheckException or a subclass """ repoman_check_name = None def __init__(self, contents): self.contents = contents pass def Run(self): """Run the check against the contents, return a sequence of errors of the form ((line, error),...) """ pass class EbuildHeaderCheck(ContentCheck): """Ensure ebuilds have proper headers Args: modification_year - Year the ebuild was last modified """ repoman_check_name = 'ebuild.badheader' gentoo_copyright = r'^# Copyright ((1999|200\d)-)?%s Gentoo Foundation$' # Why a regex here, use a string match # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$') gentoo_license = r'# Distributed under the terms of the GNU General Public License v2' cvs_header = re.compile(r'^#\s*\$Header.*\$$') def __init__(self, contents, modification_year): ContentCheck.__init__(self, contents) self.modification_year = modification_year self.gentoo_copyright_re = re.compile(self.gentoo_copyright % self.modification_year) def Run(self): """Locate simple header mistakes in an ebuild Copyright header errors CVS header errors License header errors """ errors = [] for num, line in enumerate(self.contents): if num == 0: match = self.gentoo_copyright_re.match(line) if not match: errors.append((num + 1, COPYRIGHT_ERROR)) if num == 1 and line.strip() != self.gentoo_license: errors.append((num + 1, LICENSE_ERROR)) if num == 2: match = self.cvs_header.match(line) if not match: errors.append((num + 1, CVS_HEADER_ERROR)) if num > 2: return errors return errors class EbuildWhitespaceCheck(ContentCheck): """Ensure ebuilds have proper whitespacing""" repoman_check_name = 'ebuild.minorsyn' ignore_line = re.compile(r'(^$)|(^(\t)*#)') leading_spaces = re.compile(r'^[\S\t]') trailing_whitespace = re.compile(r'.*([\S]$)') def __init__(self, contents): ContentCheck.__init__(self, contents) def Run(self): """Locate simple whitespace errors Lines with leading spaces or trailing whitespace """ errors = [] for num, line in enumerate(self.contents): match = self.ignore_line.match(line) if match: continue match = self.leading_spaces.match(line) if not match: errors.append((num + 1, LEADING_SPACES_ERROR)) match = self.trailing_whitespace.match(line) if not match: errors.append((num + 1, TRAILING_WHITESPACE_ERROR)) return errors class EbuildQuoteCheck(ContentCheck): """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc...""" repoman_check_name = 'ebuild.minorsyn' missing_quotes = re.compile(r'[^"]\${?(D|S|T|ROOT|FILESDIR|WORKDIR)}?\W') missing_quotes_exclude = re.compile(r'\[\[.*[^"]\${?(D|S|T|ROOT|FILESDIR|WORKDIR)}?\W.*\]\]') ignore_line = re.compile(r'(^$)|(^(\t)*#)') def __init__(self, contents): ContentCheck.__init__(self, contents) def Run(self): """Locate simple errors in ebuilds: Missing quotes around variables that may contain spaces """ errors = [] for num, line in enumerate(self.contents): match = self.ignore_line.match(line) if match: continue missing_quotes_line = self.missing_quotes.search(line) if missing_quotes_line: for group in missing_quotes_line.group(): match = self.missing_quotes_exclude.search(group) if not match: errors.append((num + 1, MISSING_QUOTES_ERROR)) break return errors class EbuildAssignmentCheck(ContentCheck): """Ensure ebuilds don't assign to readonly variables.""" repoman_check_name = 'ebuild.majorsyn' readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=') line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$') ignore_line = re.compile(r'(^$)|(^(\t)*#)') def __init__(self, contents): ContentCheck.__init__(self, contents) def Run(self): """Locate simple errors in ebuilds: Assigning to read-only variables. """ errors = [] previous_line = None # enumerate is 0 indexed, so add one when necesseary for num, line in enumerate(self.contents): match = self.ignore_line.match(line) if match: continue match = self.readonly_assignment.match(line) if match and (not previous_line or not self.line_continuation.match(previous_line)): errors.append((num + 1, READONLY_ASSIGNMENT_ERROR)) previous_line = line return errors