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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
"""Base classes for check results."""
from functools import total_ordering
from pkgcore.ebuild import cpv
from snakeoil import klass
from snakeoil.strings import pluralism
from . import base
from .packages import FilteredPkg, RawCPV
class InvalidResult(Exception):
"""Creating a result object failed in some fashion."""
@total_ordering
class Result:
"""Generic report result returned from a check."""
# all results are shown by default
_filtered = False
# default to repository level results
scope = base.repo_scope
# priority level, color, name, and profile type
level = None
color = None
_name = None
_profile = None
def __init_subclass__(cls, **kwargs):
"""Initialize result subclasses and set 'name' class attribute."""
super().__init_subclass__(**kwargs)
cls.name = cls._name if cls._name is not None else cls.__name__
def __str__(self):
return f'{self.name}: {self.desc}'
@property
def desc(self):
"""Result description."""
@property
def _attrs(self):
"""Return all public result attributes."""
return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
@classmethod
def _create(cls, **kwargs):
"""Create a new result object from a given attributes dict."""
if issubclass(cls, CategoryResult):
category = kwargs.pop('category', None)
package = kwargs.pop('package', None)
version = kwargs.pop('version', None)
if 'pkg' not in kwargs:
# recreate pkg param from related, separated attributes
if category is None:
raise InvalidResult('missing category')
if issubclass(cls, PackageResult) and package is None:
raise InvalidResult('missing package')
if issubclass(cls, VersionResult) and version is None:
raise InvalidResult('missing version')
kwargs['pkg'] = RawCPV(category, package, version)
return cls(**kwargs)
def __eq__(self, other):
return self.name == other.name and self._attrs == other._attrs
def __hash__(self):
return hash((self.name, tuple(sorted(self._attrs.items()))))
def __lt__(self, other):
if self.scope == other.scope:
if self.name == other.name:
return self.desc < other.desc
return self.name < other.name
return self.scope < other.scope
class AliasResult(Result):
"""Classes directly inheriting this class can be targeted as scannable keywords."""
class BaseLinesResult:
"""Base class for results of multiples lines."""
def __init__(self, lines, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lines = tuple(lines)
@property
def lines_str(self):
s = pluralism(self.lines)
lines = ', '.join(map(str, self.lines))
return f'on line{s}: {lines}'
class Error(Result):
"""Result with an error priority level."""
level = 'error'
color = 'red'
class Warning(Result):
"""Result with a warning priority level."""
level = 'warning'
color = 'yellow'
class Style(Result):
"""Result with a coding style priority level."""
level = 'style'
color = 'cyan'
class Info(Result):
"""Result with an info priority level."""
level = 'info'
color = 'green'
class CommitResult(Result):
"""Result related to a specific git commit."""
scope = base.commit_scope
def __init__(self, commit, **kwargs):
super().__init__(**kwargs)
self.commit = str(commit)
self._attr = 'commit'
def __lt__(self, other):
try:
# if hashes match, sort by name/desc
if self.commit == other.commit:
if self.name == other.name:
return self.desc < other.desc
return self.name < other.name
except AttributeError:
pass
return False
class ProfilesResult(Result):
"""Result related to profiles."""
scope = base.profiles_scope
class EclassResult(Result):
"""Result related to a specific eclass."""
scope = base.eclass_scope
def __init__(self, eclass, **kwargs):
super().__init__(**kwargs)
self.eclass = str(eclass)
self._attr = 'eclass'
def __lt__(self, other):
try:
# if eclasses match, sort by name/desc
if self.eclass == other.eclass:
if self.name == other.name:
return self.desc < other.desc
return self.name < other.name
return self.eclass < other.eclass
except AttributeError:
pass
return False
class CategoryResult(Result):
"""Result related to a specific category."""
scope = base.category_scope
def __init__(self, pkg, **kwargs):
super().__init__(**kwargs)
self.category = pkg.category
self._attr = 'category'
def __lt__(self, other):
try:
if self.category != other.category:
return self.category < other.category
except AttributeError:
pass
return super().__lt__(other)
class PackageResult(CategoryResult):
"""Result related to a specific package."""
scope = base.package_scope
def __init__(self, pkg, **kwargs):
super().__init__(pkg, **kwargs)
self.package = pkg.package
self._attr = 'package'
def __lt__(self, other):
try:
if self.category == other.category and self.package != other.package:
return self.package < other.package
except AttributeError:
pass
return super().__lt__(other)
class VersionResult(PackageResult):
"""Result related to a specific version of a package."""
scope = base.version_scope
def __init__(self, pkg, **kwargs):
if isinstance(pkg, FilteredPkg):
self._filtered = True
pkg = pkg._pkg
super().__init__(pkg, **kwargs)
self.version = pkg.fullver
self._attr = 'version'
@klass.jit_attr
def ver_rev(self):
version, _, revision = self.version.partition('-r')
revision = cpv.Revision(revision)
return version, revision
def __lt__(self, other, cmp=None):
try:
if self.category == other.category and self.package == other.package:
if cmp is None:
cmp = cpv.ver_cmp(*(self.ver_rev + other.ver_rev))
if cmp < 0:
return True
elif cmp > 0:
return False
except AttributeError:
pass
return super().__lt__(other)
class LinesResult(BaseLinesResult, VersionResult):
"""Result related to multiples lines of an ebuild."""
class LineResult(VersionResult):
"""Result related to a specific line of an ebuild."""
def __init__(self, line, lineno, **kwargs):
super().__init__(**kwargs)
self.line = line
self.lineno = lineno
def __lt__(self, other):
cmp = None
try:
if self.category == other.category and self.package == other.package:
# sort by line number for matching versions
cmp = cpv.ver_cmp(*(self.ver_rev + other.ver_rev))
if cmp == 0:
if self.lineno < other.lineno:
return True
elif self.lineno > other.lineno:
return False
except AttributeError:
pass
return super().__lt__(other, cmp=cmp)
class _LogResult(Result):
"""Message caught from a logger instance."""
def __init__(self, msg):
super().__init__()
self.msg = str(msg)
@property
def desc(self):
return self.msg
class LogWarning(_LogResult, Warning):
"""Warning caught from a logger instance."""
class LogError(_LogResult, Error):
"""Error caught from a logger instance."""
class MetadataError(Error):
"""Problem detected with a package's metadata."""
# specific metadata attributes handled by the result class
attr = None
# mapping from data attributes to result classes
results = {}
def __init_subclass__(cls, **kwargs):
"""Register metadata attribute error results."""
super().__init_subclass__(**kwargs)
if cls.attr is not None:
setting = cls.results.setdefault(cls.attr, cls)
if setting != cls:
raise ValueError(
f'metadata attribute {cls.attr!r} already registered: {setting!r}')
else:
raise ValueError(f'class missing metadata attributes: {cls!r}')
def __init__(self, attr, msg, **kwargs):
super().__init__(**kwargs)
self.attr = attr
self.msg = str(msg)
@property
def desc(self):
return self.msg
|