aboutsummaryrefslogtreecommitdiffstats
path: root/repoman/lib/repoman/modules/linechecks/controller.py
blob: 7082a5d02a1f67142707d879231b9c9ce8fadb5f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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