1 |
commit: c2cffc0b424f3afcebc973031519fc3fea4629d6 |
2 |
Author: Michał Górny <mgorny <AT> gentoo <DOT> org> |
3 |
AuthorDate: Tue Jul 17 15:39:01 2018 +0000 |
4 |
Commit: Michał Górny <mgorny <AT> gentoo <DOT> org> |
5 |
CommitDate: Tue Jul 17 15:40:51 2018 +0000 |
6 |
URL: https://gitweb.gentoo.org/proj/qa-scripts.git/commit/?id=c2cffc0b |
7 |
|
8 |
Convert pkgcheck2html to submodule |
9 |
|
10 |
.gitmodules | 3 + |
11 |
pkgcheck2html | 1 + |
12 |
pkgcheck2html/jinja2htmlcompress.py | 150 ---------------------------------- |
13 |
pkgcheck2html/output.html.jinja | 79 ------------------ |
14 |
pkgcheck2html/pkgcheck2html.conf.json | 97 ---------------------- |
15 |
pkgcheck2html/pkgcheck2html.py | 146 --------------------------------- |
16 |
6 files changed, 4 insertions(+), 472 deletions(-) |
17 |
|
18 |
diff --git a/.gitmodules b/.gitmodules |
19 |
new file mode 100644 |
20 |
index 0000000..870148a |
21 |
--- /dev/null |
22 |
+++ b/.gitmodules |
23 |
@@ -0,0 +1,3 @@ |
24 |
+[submodule "pkgcheck2html"] |
25 |
+ path = pkgcheck2html |
26 |
+ url = https://github.com/mgorny/pkgcheck-result-parser |
27 |
|
28 |
diff --git a/pkgcheck2html b/pkgcheck2html |
29 |
new file mode 160000 |
30 |
index 0000000..1bfec8e |
31 |
--- /dev/null |
32 |
+++ b/pkgcheck2html |
33 |
@@ -0,0 +1 @@ |
34 |
+Subproject commit 1bfec8e37a2a770e47ed0971c4c3684292073ace |
35 |
|
36 |
diff --git a/pkgcheck2html/jinja2htmlcompress.py b/pkgcheck2html/jinja2htmlcompress.py |
37 |
deleted file mode 100644 |
38 |
index 5dfb211..0000000 |
39 |
--- a/pkgcheck2html/jinja2htmlcompress.py |
40 |
+++ /dev/null |
41 |
@@ -1,150 +0,0 @@ |
42 |
-# -*- coding: utf-8 -*- |
43 |
-""" |
44 |
- jinja2htmlcompress |
45 |
- ~~~~~~~~~~~~~~~~~~ |
46 |
- |
47 |
- A Jinja2 extension that eliminates useless whitespace at template |
48 |
- compilation time without extra overhead. |
49 |
- |
50 |
- :copyright: (c) 2011 by Armin Ronacher. |
51 |
- :license: BSD, see LICENSE for more details. |
52 |
-""" |
53 |
-import re |
54 |
-from jinja2.ext import Extension |
55 |
-from jinja2.lexer import Token, describe_token |
56 |
-from jinja2 import TemplateSyntaxError |
57 |
- |
58 |
- |
59 |
-_tag_re = re.compile(r'(?:<(/?)([a-zA-Z0-9_-]+)\s*|(>\s*))(?s)') |
60 |
-_ws_normalize_re = re.compile(r'[ \t\r\n]+') |
61 |
- |
62 |
- |
63 |
-class StreamProcessContext(object): |
64 |
- |
65 |
- def __init__(self, stream): |
66 |
- self.stream = stream |
67 |
- self.token = None |
68 |
- self.stack = [] |
69 |
- |
70 |
- def fail(self, message): |
71 |
- raise TemplateSyntaxError(message, self.token.lineno, |
72 |
- self.stream.name, self.stream.filename) |
73 |
- |
74 |
- |
75 |
-def _make_dict_from_listing(listing): |
76 |
- rv = {} |
77 |
- for keys, value in listing: |
78 |
- for key in keys: |
79 |
- rv[key] = value |
80 |
- return rv |
81 |
- |
82 |
- |
83 |
-class HTMLCompress(Extension): |
84 |
- isolated_elements = set(['script', 'style', 'noscript', 'textarea']) |
85 |
- void_elements = set(['br', 'img', 'area', 'hr', 'param', 'input', |
86 |
- 'embed', 'col']) |
87 |
- block_elements = set(['div', 'p', 'form', 'ul', 'ol', 'li', 'table', 'tr', |
88 |
- 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th', 'dl', |
89 |
- 'dt', 'dd', 'blockquote', 'h1', 'h2', 'h3', 'h4', |
90 |
- 'h5', 'h6', 'pre']) |
91 |
- breaking_rules = _make_dict_from_listing([ |
92 |
- (['p'], set(['#block'])), |
93 |
- (['li'], set(['li'])), |
94 |
- (['td', 'th'], set(['td', 'th', 'tr', 'tbody', 'thead', 'tfoot'])), |
95 |
- (['tr'], set(['tr', 'tbody', 'thead', 'tfoot'])), |
96 |
- (['thead', 'tbody', 'tfoot'], set(['thead', 'tbody', 'tfoot'])), |
97 |
- (['dd', 'dt'], set(['dl', 'dt', 'dd'])) |
98 |
- ]) |
99 |
- |
100 |
- def is_isolated(self, stack): |
101 |
- for tag in reversed(stack): |
102 |
- if tag in self.isolated_elements: |
103 |
- return True |
104 |
- return False |
105 |
- |
106 |
- def is_breaking(self, tag, other_tag): |
107 |
- breaking = self.breaking_rules.get(other_tag) |
108 |
- return breaking and (tag in breaking or |
109 |
- ('#block' in breaking and tag in self.block_elements)) |
110 |
- |
111 |
- def enter_tag(self, tag, ctx): |
112 |
- while ctx.stack and self.is_breaking(tag, ctx.stack[-1]): |
113 |
- self.leave_tag(ctx.stack[-1], ctx) |
114 |
- if tag not in self.void_elements: |
115 |
- ctx.stack.append(tag) |
116 |
- |
117 |
- def leave_tag(self, tag, ctx): |
118 |
- if not ctx.stack: |
119 |
- ctx.fail('Tried to leave "%s" but something closed ' |
120 |
- 'it already' % tag) |
121 |
- if tag == ctx.stack[-1]: |
122 |
- ctx.stack.pop() |
123 |
- return |
124 |
- for idx, other_tag in enumerate(reversed(ctx.stack)): |
125 |
- if other_tag == tag: |
126 |
- for num in xrange(idx + 1): |
127 |
- ctx.stack.pop() |
128 |
- elif not self.breaking_rules.get(other_tag): |
129 |
- break |
130 |
- |
131 |
- def normalize(self, ctx): |
132 |
- pos = 0 |
133 |
- buffer = [] |
134 |
- def write_data(value): |
135 |
- if not self.is_isolated(ctx.stack): |
136 |
- value = _ws_normalize_re.sub(' ', value.strip()) |
137 |
- buffer.append(value) |
138 |
- |
139 |
- for match in _tag_re.finditer(ctx.token.value): |
140 |
- closes, tag, sole = match.groups() |
141 |
- preamble = ctx.token.value[pos:match.start()] |
142 |
- write_data(preamble) |
143 |
- if sole: |
144 |
- write_data(sole) |
145 |
- else: |
146 |
- buffer.append(match.group()) |
147 |
- (closes and self.leave_tag or self.enter_tag)(tag, ctx) |
148 |
- pos = match.end() |
149 |
- |
150 |
- write_data(ctx.token.value[pos:]) |
151 |
- return u''.join(buffer) |
152 |
- |
153 |
- def filter_stream(self, stream): |
154 |
- ctx = StreamProcessContext(stream) |
155 |
- for token in stream: |
156 |
- if token.type != 'data': |
157 |
- yield token |
158 |
- continue |
159 |
- ctx.token = token |
160 |
- value = self.normalize(ctx) |
161 |
- yield Token(token.lineno, 'data', value) |
162 |
- |
163 |
- |
164 |
-class SelectiveHTMLCompress(HTMLCompress): |
165 |
- |
166 |
- def filter_stream(self, stream): |
167 |
- ctx = StreamProcessContext(stream) |
168 |
- strip_depth = 0 |
169 |
- while 1: |
170 |
- if stream.current.type == 'block_begin': |
171 |
- if stream.look().test('name:strip') or \ |
172 |
- stream.look().test('name:endstrip'): |
173 |
- stream.skip() |
174 |
- if stream.current.value == 'strip': |
175 |
- strip_depth += 1 |
176 |
- else: |
177 |
- strip_depth -= 1 |
178 |
- if strip_depth < 0: |
179 |
- ctx.fail('Unexpected tag endstrip') |
180 |
- stream.skip() |
181 |
- if stream.current.type != 'block_end': |
182 |
- ctx.fail('expected end of block, got %s' % |
183 |
- describe_token(stream.current)) |
184 |
- stream.skip() |
185 |
- if strip_depth > 0 and stream.current.type == 'data': |
186 |
- ctx.token = stream.current |
187 |
- value = self.normalize(ctx) |
188 |
- yield Token(stream.current.lineno, 'data', value) |
189 |
- else: |
190 |
- yield stream.current |
191 |
- stream.next() |
192 |
|
193 |
diff --git a/pkgcheck2html/output.html.jinja b/pkgcheck2html/output.html.jinja |
194 |
deleted file mode 100644 |
195 |
index a18408c..0000000 |
196 |
--- a/pkgcheck2html/output.html.jinja |
197 |
+++ /dev/null |
198 |
@@ -1,79 +0,0 @@ |
199 |
-<!DOCTYPE html> |
200 |
-<html> |
201 |
- <head> |
202 |
- <meta charset="utf-8"/> |
203 |
- <title>Gentoo CI - QA check results</title> |
204 |
- <link rel="stylesheet" type="text/css" href="/pkgcheck2html.css" /> |
205 |
- </head> |
206 |
- |
207 |
- <body> |
208 |
- <h1>QA check results</h1> |
209 |
- |
210 |
- {% if errors or warnings or staging %} |
211 |
- <div class="nav"> |
212 |
- <h2>issues</h2> |
213 |
- |
214 |
- <ul> |
215 |
- {% for g, pkgs in errors %} |
216 |
- <li class="err heading">{{ g }}</li> |
217 |
- {% for pkg in pkgs %} |
218 |
- <li class="err"><a href="#{{ pkg|join('/') }}">{{ pkg|join('/') }}</a></li> |
219 |
- {% endfor %} |
220 |
- {% endfor %} |
221 |
- {% for g, pkgs in warnings %} |
222 |
- <li class="warn heading">{{ g }}</li> |
223 |
- {% for pkg in pkgs %} |
224 |
- <li class="warn"><a href="#{{ pkg|join('/') }}">{{ pkg|join('/') }}</a></li> |
225 |
- {% endfor %} |
226 |
- {% endfor %} |
227 |
- {% for g, pkgs in staging %} |
228 |
- <li class="staging heading">{{ g }}</li> |
229 |
- {% for pkg in pkgs %} |
230 |
- <li class="staging"><a href="#{{ pkg|join('/') }}">{{ pkg|join('/') }}</a></li> |
231 |
- {% endfor %} |
232 |
- {% endfor %} |
233 |
- </ul> |
234 |
- </div> |
235 |
- {% endif %} |
236 |
- |
237 |
- <div class="content"> |
238 |
- <table> |
239 |
- {% for g, r in results %} |
240 |
- {% set h2_id = g[0] if g else "global" %} |
241 |
- <tr><th colspan="3" class="h2" id="{{ h2_id }}"> |
242 |
- {{ g[0] if g else "Global-scope results" }} |
243 |
- <small><a href="#{{ h2_id }}">¶</a></small> |
244 |
- </th></tr> |
245 |
- |
246 |
- {% for g, r in r %} |
247 |
- {% if g[0] %} |
248 |
- {% set h3_id = g[0] + "/" + g[1] if g[1] else "_cat" %} |
249 |
- <tr><th colspan="3" class="h3" id="{{ h3_id }}"> |
250 |
- {{ g[1] if g[1] else "Category results" }} |
251 |
- <small><a href="#{{ h3_id }}">¶</a></small> |
252 |
- </th></tr> |
253 |
- {% endif %} |
254 |
- |
255 |
- {% for g, r in r %} |
256 |
- {% for rx in r %} |
257 |
- {% set class_str = "" %} |
258 |
- {% if rx.css_class %} |
259 |
- {% set class_str = ' class="' + rx.css_class + '"' %} |
260 |
- {% endif %} |
261 |
- <tr{{ class_str }}> |
262 |
- <td>{{ g[2] if loop.index == 1 else "" }}</td> |
263 |
- <td>{{ rx.class }}</td> |
264 |
- <td>{{ rx.msg|escape }}</td> |
265 |
- </tr> |
266 |
- {% endfor %} |
267 |
- {% endfor %} |
268 |
- {% endfor %} |
269 |
- {% endfor %} |
270 |
- </table> |
271 |
- </div> |
272 |
- |
273 |
- <address>Generated based on results from: {{ ts.strftime("%F %T UTC") }}</address> |
274 |
- </body> |
275 |
-</html> |
276 |
- |
277 |
-<!-- vim:se ft=jinja : --> |
278 |
|
279 |
diff --git a/pkgcheck2html/pkgcheck2html.conf.json b/pkgcheck2html/pkgcheck2html.conf.json |
280 |
deleted file mode 100644 |
281 |
index 99b54bb..0000000 |
282 |
--- a/pkgcheck2html/pkgcheck2html.conf.json |
283 |
+++ /dev/null |
284 |
@@ -1,97 +0,0 @@ |
285 |
-{ |
286 |
- "AbsoluteSymlink": "", |
287 |
- "ArchesWithoutProfiles": "", |
288 |
- "BadFilename": "warn", |
289 |
- "BadInsIntoDir": "", |
290 |
- "BadPackageUpdate": "err", |
291 |
- "BadProfileEntry": "", |
292 |
- "BadProto": "err", |
293 |
- "BadRestricts": "", |
294 |
- "CatBadlyFormedXml": "warn", |
295 |
- "CatInvalidXml": "warn", |
296 |
- "CatMetadataXmlEmptyElement": "warn", |
297 |
- "CatMetadataXmlIndentation": "", |
298 |
- "CatMetadataXmlInvalidCatRef": "warn", |
299 |
- "CatMetadataXmlInvalidPkgRef": "warn", |
300 |
- "CatMissingMetadataXml": "warn", |
301 |
- "ConflictingChksums": "err", |
302 |
- "CrappyDescription": "warn", |
303 |
- "DeprecatedChksum": "staging", |
304 |
- "DeprecatedEAPI": "", |
305 |
- "DeprecatedEclass": "", |
306 |
- "DirectorySizeViolation": "", |
307 |
- "DoubleEmptyLine": "warn", |
308 |
- "DroppedKeywords": "", |
309 |
- "DuplicateFiles": "", |
310 |
- "EmptyFile": "warn", |
311 |
- "ExecutableFile": "", |
312 |
- "GLEP73BackAlteration": "", |
313 |
- "GLEP73Conflict": "", |
314 |
- "GLEP73Immutability": "warn", |
315 |
- "GLEP73SelfConflicting": "warn", |
316 |
- "GLEP73Syntax": "warn", |
317 |
- "Glep31Violation": "warn", |
318 |
- "HttpsAvailable": "", |
319 |
- "InvalidKeywords": "err", |
320 |
- "InvalidPN": "err", |
321 |
- "InvalidUtf8": "err", |
322 |
- "LaggingStable": "", |
323 |
- "MetadataError": "err", |
324 |
- "MismatchedPN": "warn", |
325 |
- "MissingChksum": "staging", |
326 |
- "MissingLicense": "err", |
327 |
- "MissingManifest": "err", |
328 |
- "MissingSlotDep": "", |
329 |
- "MissingUri": "", |
330 |
- "MovedPackageUpdate": "err", |
331 |
- "MultiMovePackageUpdate": "warn", |
332 |
- "NoFinalNewline": "warn", |
333 |
- "NonExistentDeps": "", |
334 |
- "NonexistentProfilePath": "err", |
335 |
- "NonsolvableDeps": "err", |
336 |
- "NonsolvableDepsInDev": "staging", |
337 |
- "NonsolvableDepsInExp": "staging", |
338 |
- "NonsolvableDepsInStable": "err", |
339 |
- "OldMultiMovePackageUpdate": "", |
340 |
- "OldPackageUpdate": "warn", |
341 |
- "PkgBadlyFormedXml": "warn", |
342 |
- "PkgInvalidXml": "warn", |
343 |
- "PkgMetadataXmlEmptyElement": "warn", |
344 |
- "PkgMetadataXmlIndentation": "", |
345 |
- "PkgMetadataXmlInvalidCatRef": "warn", |
346 |
- "PkgMetadataXmlInvalidPkgRef": "warn", |
347 |
- "PkgMetadataXmlInvalidProject": "warn", |
348 |
- "PkgMissingMetadataXml": "warn", |
349 |
- "PortageInternals": "", |
350 |
- "RedundantVersion": "", |
351 |
- "RequiredUseDefaults": "", |
352 |
- "SizeViolation": "", |
353 |
- "StaleUnstable": "", |
354 |
- "StupidKeywords": "warn", |
355 |
- "TrailingEmptyLine": "warn", |
356 |
- "UnknownCategories": "err", |
357 |
- "UnknownLicenses": "err", |
358 |
- "UnknownManifest": "err", |
359 |
- "UnknownProfileArches": "", |
360 |
- "UnknownProfilePackageUse": "", |
361 |
- "UnknownProfilePackages": "", |
362 |
- "UnknownProfileStatus": "", |
363 |
- "UnknownProfileUse": "", |
364 |
- "UnnecessaryManifest": "warn", |
365 |
- "UnstableOnly": "", |
366 |
- "UnstatedIUSE": "err", |
367 |
- "UnusedEclasses": "", |
368 |
- "UnusedGlobalFlags": "", |
369 |
- "UnusedInMastersEclasses": "", |
370 |
- "UnusedInMastersGlobalFlags": "", |
371 |
- "UnusedInMastersLicenses": "", |
372 |
- "UnusedInMastersMirrors": "", |
373 |
- "UnusedLicenses": "", |
374 |
- "UnusedLocalFlags": "warn", |
375 |
- "UnusedMirrors": "", |
376 |
- "UnusedProfileDirs": "", |
377 |
- "VisibleVcsPkg": "err", |
378 |
- "VulnerablePackage": "", |
379 |
- "WhitespaceFound": "", |
380 |
- "WrongIndentFound": "warn" |
381 |
-} |
382 |
|
383 |
diff --git a/pkgcheck2html/pkgcheck2html.py b/pkgcheck2html/pkgcheck2html.py |
384 |
deleted file mode 100755 |
385 |
index ffdf76a..0000000 |
386 |
--- a/pkgcheck2html/pkgcheck2html.py |
387 |
+++ /dev/null |
388 |
@@ -1,146 +0,0 @@ |
389 |
-#!/usr/bin/env python |
390 |
-# vim:se fileencoding=utf8 : |
391 |
-# (c) 2015-2017 Michał Górny |
392 |
-# 2-clause BSD license |
393 |
- |
394 |
-import argparse |
395 |
-import collections |
396 |
-import datetime |
397 |
-import io |
398 |
-import json |
399 |
-import os |
400 |
-import os.path |
401 |
-import sys |
402 |
-import xml.etree.ElementTree |
403 |
- |
404 |
-import jinja2 |
405 |
- |
406 |
- |
407 |
-class Result(object): |
408 |
- def __init__(self, el, class_mapping): |
409 |
- self._el = el |
410 |
- self._class_mapping = class_mapping |
411 |
- |
412 |
- def __getattr__(self, key): |
413 |
- return self._el.findtext(key) or '' |
414 |
- |
415 |
- @property |
416 |
- def css_class(self): |
417 |
- return self._class_mapping.get(getattr(self, 'class'), '') |
418 |
- |
419 |
- |
420 |
-def result_sort_key(r): |
421 |
- return (r.category, r.package, r.version, getattr(r, 'class'), r.msg) |
422 |
- |
423 |
- |
424 |
-def get_results(input_paths, class_mapping): |
425 |
- for input_path in input_paths: |
426 |
- if input_path == '-': |
427 |
- input_path = sys.stdin |
428 |
- checks = xml.etree.ElementTree.parse(input_path).getroot() |
429 |
- for r in checks: |
430 |
- yield Result(r, class_mapping) |
431 |
- |
432 |
- |
433 |
-def split_result_group(it): |
434 |
- for r in it: |
435 |
- if not r.category: |
436 |
- yield ((), r) |
437 |
- elif not r.package: |
438 |
- yield ((r.category,), r) |
439 |
- elif not r.version: |
440 |
- yield ((r.category, r.package), r) |
441 |
- else: |
442 |
- yield ((r.category, r.package, r.version), r) |
443 |
- |
444 |
- |
445 |
-def group_results(it, level = 3): |
446 |
- prev_group = () |
447 |
- prev_l = [] |
448 |
- |
449 |
- for g, r in split_result_group(it): |
450 |
- if g[:level] != prev_group: |
451 |
- if prev_l: |
452 |
- yield (prev_group, prev_l) |
453 |
- prev_group = g[:level] |
454 |
- prev_l = [] |
455 |
- prev_l.append(r) |
456 |
- yield (prev_group, prev_l) |
457 |
- |
458 |
- |
459 |
-def deep_group(it, level = 1): |
460 |
- for g, r in group_results(it, level): |
461 |
- if level > 3: |
462 |
- for x in r: |
463 |
- yield x |
464 |
- else: |
465 |
- yield (g, deep_group(r, level+1)) |
466 |
- |
467 |
- |
468 |
-def find_of_class(it, cls, level = 2): |
469 |
- out = collections.defaultdict(set) |
470 |
- |
471 |
- for g, r in group_results(it, level): |
472 |
- for x in r: |
473 |
- if x.css_class == cls: |
474 |
- out[getattr(x, 'class')].add(g) |
475 |
- |
476 |
- return [(k, sorted(v)) for k, v in sorted(out.items())] |
477 |
- |
478 |
- |
479 |
-def get_result_timestamp(paths): |
480 |
- for p in paths: |
481 |
- st = os.stat(p) |
482 |
- return datetime.datetime.utcfromtimestamp(st.st_mtime) |
483 |
- |
484 |
- |
485 |
-def main(*args): |
486 |
- p = argparse.ArgumentParser() |
487 |
- p.add_argument('-o', '--output', default='-', |
488 |
- help='Output HTML file ("-" for stdout)') |
489 |
- p.add_argument('-t', '--timestamp', default=None, |
490 |
- help='Timestamp for results (git ISO8601-like UTC)') |
491 |
- p.add_argument('files', nargs='+', |
492 |
- help='Input XML files') |
493 |
- args = p.parse_args(args) |
494 |
- |
495 |
- conf_path = os.path.join(os.path.dirname(__file__), 'pkgcheck2html.conf.json') |
496 |
- with io.open(conf_path, 'r', encoding='utf8') as f: |
497 |
- class_mapping = json.load(f) |
498 |
- |
499 |
- jenv = jinja2.Environment( |
500 |
- loader=jinja2.FileSystemLoader(os.path.dirname(__file__)), |
501 |
- extensions=['jinja2htmlcompress.HTMLCompress']) |
502 |
- t = jenv.get_template('output.html.jinja') |
503 |
- |
504 |
- results = sorted(get_results(args.files, class_mapping), key=result_sort_key) |
505 |
- |
506 |
- types = {} |
507 |
- for r in results: |
508 |
- cl = getattr(r, 'class') |
509 |
- if cl not in types: |
510 |
- types[cl] = 0 |
511 |
- types[cl] += 1 |
512 |
- |
513 |
- if args.timestamp is not None: |
514 |
- ts = datetime.datetime.strptime(args.timestamp, '%Y-%m-%d %H:%M:%S') |
515 |
- else: |
516 |
- ts = get_result_timestamp(args.files) |
517 |
- |
518 |
- out = t.render( |
519 |
- results = deep_group(results), |
520 |
- warnings = find_of_class(results, 'warn'), |
521 |
- staging = find_of_class(results, 'staging'), |
522 |
- errors = find_of_class(results, 'err'), |
523 |
- ts = ts, |
524 |
- ) |
525 |
- |
526 |
- if args.output == '-': |
527 |
- sys.stdout.write(out) |
528 |
- else: |
529 |
- with io.open(args.output, 'w', encoding='utf8') as f: |
530 |
- f.write(out) |
531 |
- |
532 |
- |
533 |
-if __name__ == '__main__': |
534 |
- sys.exit(main(*sys.argv[1:])) |