summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/spec_tools/html_printer.py
diff options
context:
space:
mode:
Diffstat (limited to 'codegen/vulkan/scripts/spec_tools/html_printer.py')
-rw-r--r--codegen/vulkan/scripts/spec_tools/html_printer.py436
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: '&#x2297;', # makeIcon('times-circle'),
- MessageType.WARNING: '&#9888;', # makeIcon('exclamation-triangle'),
- MessageType.NOTE: '&#x2139;' # makeIcon('info-circle')
-}
-
-LINK_ICON = '&#128279;' # 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