import logging import operator import os import re from repoman.modules.linechecks.base import InheritEclass from repoman.modules.linechecks.config import LineChecksConfig from repoman._portage import portage # Avoid a circular import issue in py2.7 portage.proxy.lazyimport.lazyimport(globals(), 'portage.module:Modules', ) MODULES_PATH = os.path.dirname(__file__) # initial development debug info logging.debug("LineChecks module path: %s", MODULES_PATH) class LineCheckController(object): '''Initializes and runs the LineCheck checks''' def __init__(self, repo_settings, linechecks): '''Class init @param repo_settings: RepoSettings instance ''' self.repo_settings = repo_settings self.linechecks = linechecks self.config = LineChecksConfig(repo_settings) self.controller = Modules(path=MODULES_PATH, namepath="repoman.modules.linechecks") logging.debug("LineCheckController; module_names: %s", self.controller.module_names) self._constant_checks = None self._here_doc_re = re.compile(r'.*<<[-]?(\w+)\s*(>\s*\S+\s*)?$') self._ignore_comment_re = re.compile(r'^\s*#') self._continuation_re = re.compile(r'(\\)*$') def checks_init(self, experimental_inherit=False): '''Initialize the main variables @param experimental_inherit boolean ''' if not experimental_inherit: # Emulate the old eprefixify.defined and inherit.autotools checks. self._eclass_info = self.config.eclass_info else: self._eclass_info = self.config.eclass_info_experimental_inherit self._constant_checks = [] logging.debug("LineCheckController; modules: %s", self.linechecks) # Add in the pluggable modules for mod in self.linechecks: mod_class = self.controller.get_class(mod) logging.debug("LineCheckController; module_name: %s, class: %s", mod, mod_class.__name__) self._constant_checks.append(mod_class(self.config.errors)) # Add in the InheritEclass checks logging.debug("LineCheckController; eclass_info.items(): %s", list(self.config.eclass_info)) for k, kwargs in self.config.eclass_info.items(): logging.debug("LineCheckController; k: %s, kwargs: %s", k, kwargs) self._constant_checks.append( InheritEclass( k, self.config.eclass_eapi_functions, self.config.errors, **kwargs ) ) def run_checks(self, contents, pkg): '''Run the configured linechecks @param contents: the ebjuild contents to check @param pkg: the package being checked ''' if self._constant_checks is None: self.checks_init() checks = self._constant_checks here_doc_delim = None multiline = None for lc in checks: lc.new(pkg) multinum = 0 for num, line in enumerate(contents): # Check if we're inside a here-document. if here_doc_delim is not None: if here_doc_delim.match(line): here_doc_delim = None if here_doc_delim is None: here_doc = self._here_doc_re.match(line) if here_doc is not None: here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1)) if here_doc_delim is not None: continue # Unroll multiline escaped strings so that we can check things: # inherit foo bar \ # moo \ # cow # This will merge these lines like so: # inherit foo bar moo cow # A line ending with an even number of backslashes does not count, # because the last backslash is escaped. Therefore, search for an # odd number of backslashes. line_escaped = operator.sub(*self._continuation_re.search(line).span()) % 2 == 1 if multiline: # Chop off the \ and \n bytes from the previous line. multiline = multiline[:-2] + line if not line_escaped: line = multiline num = multinum multiline = None else: continue else: if line_escaped: multinum = num multiline = line continue if not line.endswith("#nowarn\n"): # Finally we have a full line to parse. is_comment = self._ignore_comment_re.match(line) is not None for lc in checks: if is_comment and lc.ignore_comment: continue if lc.check_eapi(pkg.eapi): ignore = lc.ignore_line if not ignore or not ignore.match(line): e = lc.check(num, line) if e: yield lc.repoman_check_name, e % (num + 1) for lc in checks: i = lc.end() if i is not None: for e in i: yield lc.repoman_check_name, e