public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-commits] proj/portage:repoman commit in: repoman/pym/repoman/modules/linechecks/
@ 2017-07-15  2:08 Brian Dolbec
  0 siblings, 0 replies; 2+ messages in thread
From: Brian Dolbec @ 2017-07-15  2:08 UTC (permalink / raw
  To: gentoo-commits

commit:     c51d1a7b8b3daf6db294913c3f8a7f14aeeb0d32
Author:     Brian Dolbec <dolsen <AT> gentoo <DOT> org>
AuthorDate: Sat Jul 15 00:15:22 2017 +0000
Commit:     Brian Dolbec <dolsen <AT> gentoo <DOT> org>
CommitDate: Sat Jul 15 02:08:27 2017 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=c51d1a7b

repoman: Initial creation of a new linechecks sub module plugin system

This new module system will be for splitting the multicheck module
checks into a fully configurable, plugable system.

 repoman/pym/repoman/modules/linechecks/__init__.py |   1 +
 repoman/pym/repoman/modules/linechecks/base.py     | 101 +++++++++++++++
 repoman/pym/repoman/modules/linechecks/config.py   |  94 ++++++++++++++
 .../pym/repoman/modules/linechecks/controller.py   | 140 +++++++++++++++++++++
 4 files changed, 336 insertions(+)

diff --git a/repoman/pym/repoman/modules/linechecks/__init__.py b/repoman/pym/repoman/modules/linechecks/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/__init__.py
@@ -0,0 +1 @@
+

diff --git a/repoman/pym/repoman/modules/linechecks/base.py b/repoman/pym/repoman/modules/linechecks/base.py
new file mode 100644
index 000000000..4e3d6f0b4
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/base.py
@@ -0,0 +1,101 @@
+
+import logging
+import re
+
+
+class LineCheck(object):
+	"""Run a check on a line of an ebuild."""
+	"""A regular expression to determine whether to ignore the line"""
+	ignore_line = False
+	"""True if lines containing nothing more than comments with optional
+	leading whitespace should be ignored"""
+	ignore_comment = True
+
+	def __init__(self, errors):
+		self.errors = errors
+
+	def new(self, pkg):
+		pass
+
+	def check_eapi(self, eapi):
+		"""Returns if check should be run in the given EAPI (default: True)"""
+		return True
+
+	def check(self, num, line):
+		"""Run the check on line and return error if there is one"""
+		if self.re.match(line):
+			return self.errors[self.error]
+
+	def end(self):
+		pass
+
+
+class InheritEclass(LineCheck):
+	"""
+	Base class for checking for missing inherits, as well as excess inherits.
+
+	Args:
+		eclass: Set to the name of your eclass.
+		funcs: A tuple of functions that this eclass provides.
+		comprehensive: Is the list of functions complete?
+		exempt_eclasses: If these eclasses are inherited, disable the missing
+			inherit check.
+	"""
+
+	def __init__(
+		self, eclass, eclass_eapi_functions, errors, funcs=None, comprehensive=False,
+		exempt_eclasses=None, ignore_missing=False, **kwargs):
+		self._eclass = eclass
+		self._comprehensive = comprehensive
+		self._exempt_eclasses = exempt_eclasses
+		self._ignore_missing = ignore_missing
+		self.errors = errors
+		inherit_re = eclass
+		self._eclass_eapi_functions = eclass_eapi_functions
+		self._inherit_re = re.compile(
+			r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
+		# Match when the function is preceded only by leading whitespace, a
+		# shell operator such as (, {, |, ||, or &&, or optional variable
+		# setting(s). This prevents false positives in things like elog
+		# messages, as reported in bug #413285.
+		logging.debug("InheritEclass, eclass: %s, funcs: %s", eclass, funcs)
+		self._func_re = re.compile(
+			r'(^|[|&{(])\s*(\w+=.*)?\b(' + r'|'.join(funcs) + r')\b')
+
+	def new(self, pkg):
+		self.repoman_check_name = 'inherit.missing'
+		# We can't use pkg.inherited because that tells us all the eclasses that
+		# have been inherited and not just the ones we inherit directly.
+		self._inherit = False
+		self._func_call = False
+		if self._exempt_eclasses is not None:
+			inherited = pkg.inherited
+			self._disabled = any(x in inherited for x in self._exempt_eclasses)
+		else:
+			self._disabled = False
+		self._eapi = pkg.eapi
+
+	def check(self, num, line):
+		if not self._inherit:
+			self._inherit = self._inherit_re.match(line)
+		if not self._inherit:
+			if self._disabled or self._ignore_missing:
+				return
+			s = self._func_re.search(line)
+			if s is not None:
+				func_name = s.group(3)
+				eapi_func = self._eclass_eapi_functions.get(func_name)
+				if eapi_func is None or not eapi_func(self._eapi):
+					self._func_call = True
+					return (
+						'%s.eclass is not inherited, '
+						'but "%s" found at line: %s' %
+						(self._eclass, func_name, '%d'))
+		elif not self._func_call:
+			self._func_call = self._func_re.search(line)
+
+	def end(self):
+		if not self._disabled and self._comprehensive and self._inherit \
+			and not self._func_call:
+			self.repoman_check_name = 'inherit.unused'
+			yield 'no function called from %s.eclass; please drop' % self._eclass

diff --git a/repoman/pym/repoman/modules/linechecks/config.py b/repoman/pym/repoman/modules/linechecks/config.py
new file mode 100644
index 000000000..0044afe79
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/config.py
@@ -0,0 +1,94 @@
+# -*- coding:utf-8 -*-
+# repoman: Checks
+# Copyright 2007-2017 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+"""This module contains functions used in Repoman to ascertain the quality
+and correctness of an ebuild."""
+
+from __future__ import unicode_literals
+
+import collections
+import logging
+import os
+import yaml
+from copy import deepcopy
+
+from portage.util import stack_lists
+from repoman.config import load_config
+
+
+def merge(dict1, dict2):
+    ''' Return a new dictionary by merging two dictionaries recursively. '''
+
+    result = deepcopy(dict1)
+
+    for key, value in dict2.items():
+        if isinstance(value, collections.Mapping):
+            result[key] = merge(result.get(key, {}), value)
+        else:
+            result[key] = deepcopy(dict2[key])
+
+    return result
+
+
+class LineChecksConfig(object):
+	'''Holds our LineChecks configuration data and operation functions'''
+
+	def __init__(self, repo_settings):
+		'''Class init
+
+		@param repo_settings: RepoSettings instance
+		@param configpaths: ordered list of filepaths to load
+		'''
+		self.repo_settings = repo_settings
+		self.infopaths = [os.path.join(path, 'linechecks.yaml') for path in self.repo_settings.masters_list]
+		logging.debug("LineChecksConfig; configpaths: %s", self.infopaths)
+		self.info_config = None
+		self._config = None
+		self.usex_supported_eapis = None
+		self.in_iuse_supported_eapis = None
+		self.get_libdir_supported_eapis = None
+		self.eclass_eapi_functions = {}
+		self.eclass_export_functions = None
+		self.eclass_info = {}
+		self.eclass_info_experimental_inherit = {}
+		self.errors = {}
+		self.load_checks_info()
+
+
+	def load_checks_info(self, infopaths=None):
+		'''load the config files in order
+
+		@param configpaths: ordered list of filepaths to load
+		'''
+		if infopaths:
+			self.infopaths = infopaths
+		elif not self.infopaths:
+			logging.error("LineChecksConfig; Error: No linechecks.yaml files defined")
+		configs = load_config(self.infopaths, 'yaml')
+		if configs == {}:
+			logging.error("LineChecksConfig: Failed to load a valid 'linechecks.yaml' file at paths: %s", self.infopaths)
+			return False
+		logging.debug("LineChecksConfig: linechecks.yaml configs: %s", configs)
+		self.info_config = configs
+
+		self.errors = self.info_config['errors']
+		self.usex_supported_eapis = self.info_config['usex_supported_eapis']
+		self.in_iuse_supported_eapis = self.info_config['in_iuse_supported_eapis']
+		self.eclass_info_experimental_inherit = self.info_config['eclass_info_experimental_inherit']
+		self.get_libdir_supported_eapis = self.in_iuse_supported_eapis
+		self.eclass_eapi_functions = {
+			"usex": lambda eapi: eapi not in self.usex_supported_eapis,
+			"in_iuse": lambda eapi: eapi not in self.in_iuse_supported_eapis,
+			"get_libdir": lambda eapi: eapi not in self.get_libdir_supported_eapis,
+		}
+
+		# eclasses that export ${ECLASS}_src_(compile|configure|install)
+		self.eclass_export_functions = self.info_config['eclass_export_functions']
+
+		self.eclass_info_experimental_inherit = self.info_config['eclass_info_experimental_inherit']
+		# These are "eclasses are the whole ebuild" type thing.
+		self.eclass_info_experimental_inherit['eutils']['exempt_eclasses'] = self.eclass_export_functions
+		self.eclass_info_experimental_inherit['multilib']['exempt_eclasses'] = self.eclass_export_functions + [
+						'autotools', 'libtool', 'multilib-minimal']

diff --git a/repoman/pym/repoman/modules/linechecks/controller.py b/repoman/pym/repoman/modules/linechecks/controller.py
new file mode 100644
index 000000000..5c7f5c924
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/controller.py
@@ -0,0 +1,140 @@
+
+import logging
+import operator
+import os
+import re
+
+from portage.module import Modules
+from repoman.modules.linechecks.base import InheritEclass
+from repoman.modules.linechecks.config import LineChecksConfig
+
+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


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* [gentoo-commits] proj/portage:repoman commit in: repoman/pym/repoman/modules/linechecks/
@ 2017-07-15  2:29 Brian Dolbec
  0 siblings, 0 replies; 2+ messages in thread
From: Brian Dolbec @ 2017-07-15  2:29 UTC (permalink / raw
  To: gentoo-commits

commit:     b6e8cfe29d860d018bb386cdfbbcde6cb2edaadc
Author:     Brian Dolbec <dolsen <AT> gentoo <DOT> org>
AuthorDate: Sat Jul 15 00:15:22 2017 +0000
Commit:     Brian Dolbec <dolsen <AT> gentoo <DOT> org>
CommitDate: Sat Jul 15 02:25:44 2017 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=b6e8cfe2

repoman: Initial creation of a new linechecks sub module plugin system

This new module system will be for splitting the multicheck module
checks into a fully configurable, plugable system.

 repoman/pym/repoman/modules/linechecks/__init__.py |   1 +
 repoman/pym/repoman/modules/linechecks/base.py     | 101 +++++++++++++++
 repoman/pym/repoman/modules/linechecks/config.py   |  94 ++++++++++++++
 .../pym/repoman/modules/linechecks/controller.py   | 140 +++++++++++++++++++++
 4 files changed, 336 insertions(+)

diff --git a/repoman/pym/repoman/modules/linechecks/__init__.py b/repoman/pym/repoman/modules/linechecks/__init__.py
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/__init__.py
@@ -0,0 +1 @@
+

diff --git a/repoman/pym/repoman/modules/linechecks/base.py b/repoman/pym/repoman/modules/linechecks/base.py
new file mode 100644
index 000000000..4e3d6f0b4
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/base.py
@@ -0,0 +1,101 @@
+
+import logging
+import re
+
+
+class LineCheck(object):
+	"""Run a check on a line of an ebuild."""
+	"""A regular expression to determine whether to ignore the line"""
+	ignore_line = False
+	"""True if lines containing nothing more than comments with optional
+	leading whitespace should be ignored"""
+	ignore_comment = True
+
+	def __init__(self, errors):
+		self.errors = errors
+
+	def new(self, pkg):
+		pass
+
+	def check_eapi(self, eapi):
+		"""Returns if check should be run in the given EAPI (default: True)"""
+		return True
+
+	def check(self, num, line):
+		"""Run the check on line and return error if there is one"""
+		if self.re.match(line):
+			return self.errors[self.error]
+
+	def end(self):
+		pass
+
+
+class InheritEclass(LineCheck):
+	"""
+	Base class for checking for missing inherits, as well as excess inherits.
+
+	Args:
+		eclass: Set to the name of your eclass.
+		funcs: A tuple of functions that this eclass provides.
+		comprehensive: Is the list of functions complete?
+		exempt_eclasses: If these eclasses are inherited, disable the missing
+			inherit check.
+	"""
+
+	def __init__(
+		self, eclass, eclass_eapi_functions, errors, funcs=None, comprehensive=False,
+		exempt_eclasses=None, ignore_missing=False, **kwargs):
+		self._eclass = eclass
+		self._comprehensive = comprehensive
+		self._exempt_eclasses = exempt_eclasses
+		self._ignore_missing = ignore_missing
+		self.errors = errors
+		inherit_re = eclass
+		self._eclass_eapi_functions = eclass_eapi_functions
+		self._inherit_re = re.compile(
+			r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
+		# Match when the function is preceded only by leading whitespace, a
+		# shell operator such as (, {, |, ||, or &&, or optional variable
+		# setting(s). This prevents false positives in things like elog
+		# messages, as reported in bug #413285.
+		logging.debug("InheritEclass, eclass: %s, funcs: %s", eclass, funcs)
+		self._func_re = re.compile(
+			r'(^|[|&{(])\s*(\w+=.*)?\b(' + r'|'.join(funcs) + r')\b')
+
+	def new(self, pkg):
+		self.repoman_check_name = 'inherit.missing'
+		# We can't use pkg.inherited because that tells us all the eclasses that
+		# have been inherited and not just the ones we inherit directly.
+		self._inherit = False
+		self._func_call = False
+		if self._exempt_eclasses is not None:
+			inherited = pkg.inherited
+			self._disabled = any(x in inherited for x in self._exempt_eclasses)
+		else:
+			self._disabled = False
+		self._eapi = pkg.eapi
+
+	def check(self, num, line):
+		if not self._inherit:
+			self._inherit = self._inherit_re.match(line)
+		if not self._inherit:
+			if self._disabled or self._ignore_missing:
+				return
+			s = self._func_re.search(line)
+			if s is not None:
+				func_name = s.group(3)
+				eapi_func = self._eclass_eapi_functions.get(func_name)
+				if eapi_func is None or not eapi_func(self._eapi):
+					self._func_call = True
+					return (
+						'%s.eclass is not inherited, '
+						'but "%s" found at line: %s' %
+						(self._eclass, func_name, '%d'))
+		elif not self._func_call:
+			self._func_call = self._func_re.search(line)
+
+	def end(self):
+		if not self._disabled and self._comprehensive and self._inherit \
+			and not self._func_call:
+			self.repoman_check_name = 'inherit.unused'
+			yield 'no function called from %s.eclass; please drop' % self._eclass

diff --git a/repoman/pym/repoman/modules/linechecks/config.py b/repoman/pym/repoman/modules/linechecks/config.py
new file mode 100644
index 000000000..0044afe79
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/config.py
@@ -0,0 +1,94 @@
+# -*- coding:utf-8 -*-
+# repoman: Checks
+# Copyright 2007-2017 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+"""This module contains functions used in Repoman to ascertain the quality
+and correctness of an ebuild."""
+
+from __future__ import unicode_literals
+
+import collections
+import logging
+import os
+import yaml
+from copy import deepcopy
+
+from portage.util import stack_lists
+from repoman.config import load_config
+
+
+def merge(dict1, dict2):
+    ''' Return a new dictionary by merging two dictionaries recursively. '''
+
+    result = deepcopy(dict1)
+
+    for key, value in dict2.items():
+        if isinstance(value, collections.Mapping):
+            result[key] = merge(result.get(key, {}), value)
+        else:
+            result[key] = deepcopy(dict2[key])
+
+    return result
+
+
+class LineChecksConfig(object):
+	'''Holds our LineChecks configuration data and operation functions'''
+
+	def __init__(self, repo_settings):
+		'''Class init
+
+		@param repo_settings: RepoSettings instance
+		@param configpaths: ordered list of filepaths to load
+		'''
+		self.repo_settings = repo_settings
+		self.infopaths = [os.path.join(path, 'linechecks.yaml') for path in self.repo_settings.masters_list]
+		logging.debug("LineChecksConfig; configpaths: %s", self.infopaths)
+		self.info_config = None
+		self._config = None
+		self.usex_supported_eapis = None
+		self.in_iuse_supported_eapis = None
+		self.get_libdir_supported_eapis = None
+		self.eclass_eapi_functions = {}
+		self.eclass_export_functions = None
+		self.eclass_info = {}
+		self.eclass_info_experimental_inherit = {}
+		self.errors = {}
+		self.load_checks_info()
+
+
+	def load_checks_info(self, infopaths=None):
+		'''load the config files in order
+
+		@param configpaths: ordered list of filepaths to load
+		'''
+		if infopaths:
+			self.infopaths = infopaths
+		elif not self.infopaths:
+			logging.error("LineChecksConfig; Error: No linechecks.yaml files defined")
+		configs = load_config(self.infopaths, 'yaml')
+		if configs == {}:
+			logging.error("LineChecksConfig: Failed to load a valid 'linechecks.yaml' file at paths: %s", self.infopaths)
+			return False
+		logging.debug("LineChecksConfig: linechecks.yaml configs: %s", configs)
+		self.info_config = configs
+
+		self.errors = self.info_config['errors']
+		self.usex_supported_eapis = self.info_config['usex_supported_eapis']
+		self.in_iuse_supported_eapis = self.info_config['in_iuse_supported_eapis']
+		self.eclass_info_experimental_inherit = self.info_config['eclass_info_experimental_inherit']
+		self.get_libdir_supported_eapis = self.in_iuse_supported_eapis
+		self.eclass_eapi_functions = {
+			"usex": lambda eapi: eapi not in self.usex_supported_eapis,
+			"in_iuse": lambda eapi: eapi not in self.in_iuse_supported_eapis,
+			"get_libdir": lambda eapi: eapi not in self.get_libdir_supported_eapis,
+		}
+
+		# eclasses that export ${ECLASS}_src_(compile|configure|install)
+		self.eclass_export_functions = self.info_config['eclass_export_functions']
+
+		self.eclass_info_experimental_inherit = self.info_config['eclass_info_experimental_inherit']
+		# These are "eclasses are the whole ebuild" type thing.
+		self.eclass_info_experimental_inherit['eutils']['exempt_eclasses'] = self.eclass_export_functions
+		self.eclass_info_experimental_inherit['multilib']['exempt_eclasses'] = self.eclass_export_functions + [
+						'autotools', 'libtool', 'multilib-minimal']

diff --git a/repoman/pym/repoman/modules/linechecks/controller.py b/repoman/pym/repoman/modules/linechecks/controller.py
new file mode 100644
index 000000000..5c7f5c924
--- /dev/null
+++ b/repoman/pym/repoman/modules/linechecks/controller.py
@@ -0,0 +1,140 @@
+
+import logging
+import operator
+import os
+import re
+
+from portage.module import Modules
+from repoman.modules.linechecks.base import InheritEclass
+from repoman.modules.linechecks.config import LineChecksConfig
+
+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


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2017-07-15  2:29 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-07-15  2:29 [gentoo-commits] proj/portage:repoman commit in: repoman/pym/repoman/modules/linechecks/ Brian Dolbec
  -- strict thread matches above, loose matches on Subject: below --
2017-07-15  2:08 Brian Dolbec

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox