Gentoo Archives: gentoo-commits

From: "Michał Górny" <mgorny@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/qa-scripts:master commit in: /, pkgcheck2html/
Date: Tue, 17 Jul 2018 15:41:03
Message-Id: 1531842051.c2cffc0b424f3afcebc973031519fc3fea4629d6.mgorny@gentoo
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:]))