diff options
Diffstat (limited to 'codegen/vulkan/scripts/spec_tools/html_printer.py')
-rw-r--r-- | codegen/vulkan/scripts/spec_tools/html_printer.py | 436 |
1 files changed, 0 insertions, 436 deletions
diff --git a/codegen/vulkan/scripts/spec_tools/html_printer.py b/codegen/vulkan/scripts/spec_tools/html_printer.py deleted file mode 100644 index eb1df406..00000000 --- a/codegen/vulkan/scripts/spec_tools/html_printer.py +++ /dev/null @@ -1,436 +0,0 @@ -"""Defines HTMLPrinter, a BasePrinter subclass for a single-page HTML results file.""" - -# Copyright (c) 2018-2019 Collabora, Ltd. -# -# SPDX-License-Identifier: Apache-2.0 -# -# Author(s): Ryan Pavlik <ryan.pavlik@collabora.com> - -import html -import re -from collections import namedtuple - -from .base_printer import BasePrinter, getColumn -from .shared import (MessageContext, MessageType, generateInclude, - getHighlightedRange) - -# Bootstrap styles (for constructing CSS class names) associated with MessageType values. -MESSAGE_TYPE_STYLES = { - MessageType.ERROR: 'danger', - MessageType.WARNING: 'warning', - MessageType.NOTE: 'secondary' -} - - -# HTML Entity for a little emoji-icon associated with MessageType values. -MESSAGE_TYPE_ICONS = { - MessageType.ERROR: '⊗', # makeIcon('times-circle'), - MessageType.WARNING: '⚠', # makeIcon('exclamation-triangle'), - MessageType.NOTE: 'ℹ' # makeIcon('info-circle') -} - -LINK_ICON = '🔗' # link icon - - -class HTMLPrinter(BasePrinter): - """Implementation of BasePrinter for generating diagnostic reports in HTML format. - - Generates a single file containing neatly-formatted messages. - - The HTML file loads Bootstrap 4 as well as 'prism' syntax highlighting from CDN. - """ - - def __init__(self, filename): - """Construct by opening the file.""" - self.f = open(filename, 'w', encoding='utf-8') - self.f.write("""<!doctype html> - <html lang="en"><head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/themes/prism.min.css" integrity="sha256-N1K43s+8twRa+tzzoF3V8EgssdDiZ6kd9r8Rfgg8kZU=" crossorigin="anonymous" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/plugins/line-numbers/prism-line-numbers.min.css" integrity="sha256-Afz2ZJtXw+OuaPX10lZHY7fN1+FuTE/KdCs+j7WZTGc=" crossorigin="anonymous" /> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/plugins/line-highlight/prism-line-highlight.min.css" integrity="sha256-FFGTaA49ZxFi2oUiWjxtTBqoda+t1Uw8GffYkdt9aco=" crossorigin="anonymous" /> - <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> - <style> - pre { - overflow-x: scroll; - white-space: nowrap; - } - </style> - <title>check_spec_links results</title> - </head> - <body> - <div class="container"> - <h1><code>check_spec_links.py</code> Scan Results</h1> - """) - # - self.filenameTransformer = re.compile(r'[^\w]+') - self.fileRange = {} - self.fileLines = {} - self.backLink = namedtuple( - 'BackLink', ['lineNum', 'col', 'end_col', 'target', 'tooltip', 'message_type']) - self.fileBackLinks = {} - - self.nextAnchor = 0 - super().__init__() - - def close(self): - """Write the tail end of the file and close it.""" - self.f.write(""" - </div> - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/prism.min.js" integrity="sha256-jc6y1s/Y+F+78EgCT/lI2lyU7ys+PFYrRSJ6q8/R8+o=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/plugins/keep-markup/prism-keep-markup.min.js" integrity="sha256-mP5i3m+wTxxOYkH+zXnKIG5oJhXLIPQYoiicCV1LpkM=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/components/prism-asciidoc.min.js" integrity="sha256-NHPE1p3VBIdXkmfbkf/S0hMA6b4Ar4TAAUlR+Rlogoc=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/plugins/line-numbers/prism-line-numbers.min.js" integrity="sha256-JfF9MVfGdRUxzT4pecjOZq6B+F5EylLQLwcQNg+6+Qk=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.15.0/plugins/line-highlight/prism-line-highlight.min.js" integrity="sha256-DEl9ZQE+lseY13oqm2+mlUr+sVI18LG813P+kzzIm8o=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js" integrity="sha256-3edrmyuQ0w65f8gfBsqowzjJe2iM6n0nKciPUp8y+7E=" crossorigin="anonymous"></script> - <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/esm/popper.min.js" integrity="sha256-T0gPN+ySsI9ixTd/1ciLl2gjdLJLfECKvkQjJn98lOs=" crossorigin="anonymous"></script> - <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> - <script> - $(function () { - $('[data-toggle="tooltip"]').tooltip(); - function autoExpand() { - var hash = window.location.hash; - if (!hash) { - return; - } - $(hash).parents().filter('.collapse').collapse('show'); - } - window.addEventListener('hashchange', autoExpand); - $(document).ready(autoExpand); - $('.accordion').on('shown.bs.collapse', function(e) { - e.target.parentNode.scrollIntoView(); - }) - }) - </script> - </body></html> - """) - self.f.close() - - ### - # Output methods: these all write to the HTML file. - def outputResults(self, checker, broken_links=True, - missing_includes=False): - """Output the full results of a checker run. - - Includes the diagnostics, broken links (if desired), - missing includes (if desired), and excerpts of all files with diagnostics. - """ - self.output(checker) - self.outputBrokenAndMissing( - checker, broken_links=broken_links, missing_includes=missing_includes) - - self.f.write(""" - <div class="container"> - <h2>Excerpts of referenced files</h2>""") - for fn in self.fileRange: - self.outputFileExcerpt(fn) - self.f.write('</div><!-- .container -->\n') - - def outputChecker(self, checker): - """Output the contents of a MacroChecker object. - - Starts and ends the accordion populated by outputCheckerFile(). - """ - self.f.write( - '<div class="container"><h2>Per-File Warnings and Errors</h2>\n') - self.f.write('<div class="accordion" id="fileAccordion">\n') - super(HTMLPrinter, self).outputChecker(checker) - self.f.write("""</div><!-- #fileAccordion --> - </div><!-- .container -->\n""") - - def outputCheckerFile(self, fileChecker): - """Output the contents of a MacroCheckerFile object. - - Stashes the lines of the file for later excerpts, - and outputs any diagnostics in an accordion card. - """ - # Save lines for later - self.fileLines[fileChecker.filename] = fileChecker.lines - - if not fileChecker.numDiagnostics(): - return - - self.f.write(""" - <div class="card"> - <div class="card-header" id="{id}-file-heading"> - <div class="row"> - <div class="col"> - <button data-target="#collapse-{id}" class="btn btn-link btn-primary mb-0 collapsed" type="button" data-toggle="collapse" aria-expanded="false" aria-controls="collapse-{id}"> - {relativefn} - </button> - </div> - """.format(id=self.makeIdentifierFromFilename(fileChecker.filename), relativefn=html.escape(self.getRelativeFilename(fileChecker.filename)))) - self.f.write('<div class="col-1">') - warnings = fileChecker.numMessagesOfType(MessageType.WARNING) - if warnings > 0: - self.f.write("""<span class="badge badge-warning" data-toggle="tooltip" title="{num} warnings in this file"> - {icon} - {num}<span class="sr-only"> warnings</span></span>""".format(num=warnings, icon=MESSAGE_TYPE_ICONS[MessageType.WARNING])) - self.f.write('</div>\n<div class="col-1">') - errors = fileChecker.numMessagesOfType(MessageType.ERROR) - if errors > 0: - self.f.write("""<span class="badge badge-danger" data-toggle="tooltip" title="{num} errors in this file"> - {icon} - {num}<span class="sr-only"> errors</span></span>""".format(num=errors, icon=MESSAGE_TYPE_ICONS[MessageType.ERROR])) - self.f.write(""" - </div><!-- .col-1 --> - </div><!-- .row --> - </div><!-- .card-header --> - <div id="collapse-{id}" class="collapse" aria-labelledby="{id}-file-heading" data-parent="#fileAccordion"> - <div class="card-body"> - """.format(id=self.makeIdentifierFromFilename(fileChecker.filename))) - super(HTMLPrinter, self).outputCheckerFile(fileChecker) - - self.f.write(""" - </div><!-- .card-body --> - </div><!-- .collapse --> - </div><!-- .card --> - <!-- ..................................... --> - """.format(id=self.makeIdentifierFromFilename(fileChecker.filename))) - - def outputMessage(self, msg): - """Output a Message.""" - anchor = self.getUniqueAnchor() - - self.recordUsage(msg.context, - linkBackTarget=anchor, - linkBackTooltip='{}: {} [...]'.format( - msg.message_type, msg.message[0]), - linkBackType=msg.message_type) - - self.f.write(""" - <div class="card"> - <div class="card-body"> - <h5 class="card-header bg bg-{style}" id="{anchor}">{icon} {t} Line {lineNum}, Column {col} (-{arg})</h5> - <p class="card-text"> - """.format( - anchor=anchor, - icon=MESSAGE_TYPE_ICONS[msg.message_type], - style=MESSAGE_TYPE_STYLES[msg.message_type], - t=self.formatBrief(msg.message_type), - lineNum=msg.context.lineNum, - col=getColumn(msg.context), - arg=msg.message_id.enable_arg())) - self.f.write(self.formatContext(msg.context)) - self.f.write('<br/>') - for line in msg.message: - self.f.write(html.escape(line)) - self.f.write('<br />\n') - self.f.write('</p>\n') - if msg.see_also: - self.f.write('<p>See also:</p><ul>\n') - for see in msg.see_also: - if isinstance(see, MessageContext): - self.f.write( - '<li>{}</li>\n'.format(self.formatContext(see))) - self.recordUsage(see, - linkBackTarget=anchor, - linkBackType=MessageType.NOTE, - linkBackTooltip='see-also associated with {} at {}'.format(msg.message_type, self.formatContextBrief(see))) - else: - self.f.write('<li>{}</li>\n'.format(self.formatBrief(see))) - self.f.write('</ul>') - if msg.replacement is not None: - self.f.write( - '<div class="alert alert-primary">Hover the highlight text to view suggested replacement.</div>') - if msg.fix is not None: - self.f.write( - '<div class="alert alert-info">Note: Auto-fix available.</div>') - if msg.script_location: - self.f.write( - '<p>Message originated at <code>{}</code></p>'.format(msg.script_location)) - self.f.write('<pre class="line-numbers language-asciidoc" data-start="{}"><code>'.format( - msg.context.lineNum)) - highlightStart, highlightEnd = getHighlightedRange(msg.context) - self.f.write(html.escape(msg.context.line[:highlightStart])) - self.f.write( - '<span class="border border-{}"'.format(MESSAGE_TYPE_STYLES[msg.message_type])) - if msg.replacement is not None: - self.f.write( - ' data-toggle="tooltip" title="{}"'.format(msg.replacement)) - self.f.write('>') - self.f.write(html.escape( - msg.context.line[highlightStart:highlightEnd])) - self.f.write('</span>') - self.f.write(html.escape(msg.context.line[highlightEnd:])) - self.f.write('</code></pre></div></div>') - - def outputBrokenLinks(self, checker, broken): - """Output a table of broken links. - - Called by self.outputBrokenAndMissing() if requested. - """ - self.f.write(""" - <div class="container"> - <h2>Missing Referenced API Includes</h2> - <p>Items here have been referenced by a linking macro, so these are all broken links in the spec!</p> - <table class="table table-striped"> - <thead> - <th scope="col">Add line to include this file</th> - <th scope="col">or add this macro instead</th> - <th scope="col">Links to this entity</th></thead> - """) - - for entity_name, uses in sorted(broken.items()): - category = checker.findEntity(entity_name).category - anchor = self.getUniqueAnchor() - asciidocAnchor = '[[{}]]'.format(entity_name) - include = generateInclude(dir_traverse='../../generated/', - generated_type='api', - category=category, - entity=entity_name) - self.f.write(""" - <tr id={}> - <td><code class="text-dark language-asciidoc">{}</code></td> - <td><code class="text-dark">{}</code></td> - <td><ul class="list-inline"> - """.format(anchor, include, asciidocAnchor)) - for context in uses: - self.f.write( - '<li class="list-inline-item">{}</li>'.format(self.formatContext(context, MessageType.NOTE))) - self.recordUsage( - context, - linkBackTooltip='Link broken in spec: {} not seen'.format( - include), - linkBackTarget=anchor, - linkBackType=MessageType.NOTE) - self.f.write("""</ul></td></tr>""") - self.f.write("""</table></div>""") - - def outputMissingIncludes(self, checker, missing): - """Output a table of missing includes. - - Called by self.outputBrokenAndMissing() if requested. - """ - self.f.write(""" - <div class="container"> - <h2>Missing Unreferenced API Includes</h2> - <p>These items are expected to be generated in the spec build process, but aren't included. - However, as they also are not referenced by any linking macros, they aren't broken links - at worst they are undocumented entities, - at best they are errors in <code>check_spec_links.py</code> logic computing which entities get generated files.</p> - <table class="table table-striped"> - <thead> - <th scope="col">Add line to include this file</th> - <th scope="col">or add this macro instead</th> - """) - - for entity in sorted(missing): - fn = checker.findEntity(entity).filename - anchor = '[[{}]]'.format(entity) - self.f.write(""" - <tr> - <td><code class="text-dark">{filename}</code></td> - <td><code class="text-dark">{anchor}</code></td> - """.format(filename=fn, anchor=anchor)) - self.f.write("""</table></div>""") - - def outputFileExcerpt(self, filename): - """Output a card containing an excerpt of a file, sufficient to show locations of all diagnostics plus some context. - - Called by self.outputResults(). - """ - self.f.write("""<div class="card"> - <div class="card-header" id="heading-{id}"><h5 class="mb-0"> - <button class="btn btn-link" type="button"> - {fn} - </button></h5></div><!-- #heading-{id} --> - <div class="card-body"> - """.format(id=self.makeIdentifierFromFilename(filename), fn=self.getRelativeFilename(filename))) - lines = self.fileLines[filename] - r = self.fileRange[filename] - self.f.write("""<pre class="line-numbers language-asciidoc line-highlight" id="excerpt-{id}" data-start="{start}"><code>""".format( - id=self.makeIdentifierFromFilename(filename), - start=r.start)) - for lineNum, line in enumerate( - lines[(r.start - 1):(r.stop - 1)], r.start): - # self.f.write(line) - lineLinks = [x for x in self.fileBackLinks[filename] - if x.lineNum == lineNum] - for col, char in enumerate(line): - colLinks = (x for x in lineLinks if x.col == col) - for link in colLinks: - # TODO right now the syntax highlighting is interfering with the link! so the link-generation is commented out, - # only generating the emoji icon. - - # self.f.write('<a href="#{target}" title="{title}" data-toggle="tooltip" data-container="body">{icon}'.format( - # target=link.target, title=html.escape(link.tooltip), - # icon=MESSAGE_TYPE_ICONS[link.message_type])) - self.f.write(MESSAGE_TYPE_ICONS[link.message_type]) - self.f.write('<span class="sr-only">Cross reference: {t} {title}</span>'.format( - title=html.escape(link.tooltip, False), t=link.message_type)) - - # self.f.write('</a>') - - # Write the actual character - self.f.write(html.escape(char)) - self.f.write('\n') - - self.f.write('</code></pre>') - self.f.write('</div><!-- .card-body -->\n') - self.f.write('</div><!-- .card -->\n') - - def outputFallback(self, obj): - """Output some text in a general way.""" - self.f.write(obj) - - ### - # Format method: return a string. - def formatContext(self, context, message_type=None): - """Format a message context in a verbose way.""" - if message_type is None: - icon = LINK_ICON - else: - icon = MESSAGE_TYPE_ICONS[message_type] - return 'In context: <a href="{href}">{icon}{relative}:{lineNum}:{col}</a>'.format( - href=self.getAnchorLinkForContext(context), - icon=icon, - # id=self.makeIdentifierFromFilename(context.filename), - relative=self.getRelativeFilename(context.filename), - lineNum=context.lineNum, - col=getColumn(context)) - - ### - # Internal methods: not mandated by parent class. - def recordUsage(self, context, linkBackTooltip=None, - linkBackTarget=None, linkBackType=MessageType.NOTE): - """Internally record a 'usage' of something. - - Increases the range of lines that are included in the excerpts, - and records back-links if appropriate. - """ - BEFORE_CONTEXT = 6 - AFTER_CONTEXT = 3 - # Clamp because we need accurate start line number to make line number - # display right - start = max(1, context.lineNum - BEFORE_CONTEXT) - stop = context.lineNum + AFTER_CONTEXT + 1 - if context.filename not in self.fileRange: - self.fileRange[context.filename] = range(start, stop) - self.fileBackLinks[context.filename] = [] - else: - oldRange = self.fileRange[context.filename] - self.fileRange[context.filename] = range( - min(start, oldRange.start), max(stop, oldRange.stop)) - - if linkBackTarget is not None: - start_col, end_col = getHighlightedRange(context) - self.fileBackLinks[context.filename].append(self.backLink( - lineNum=context.lineNum, col=start_col, end_col=end_col, - target=linkBackTarget, tooltip=linkBackTooltip, - message_type=linkBackType)) - - def makeIdentifierFromFilename(self, fn): - """Compute an acceptable HTML anchor name from a filename.""" - return self.filenameTransformer.sub('_', self.getRelativeFilename(fn)) - - def getAnchorLinkForContext(self, context): - """Compute the anchor link to the excerpt for a MessageContext.""" - return '#excerpt-{}.{}'.format( - self.makeIdentifierFromFilename(context.filename), context.lineNum) - - def getUniqueAnchor(self): - """Create and return a new unique string usable as a link anchor.""" - anchor = 'anchor-{}'.format(self.nextAnchor) - self.nextAnchor += 1 - return anchor |