blob: 3ee18f387a480c5d88aacdc0e224e7db7cdc17df [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""
Generate HTML documentation for Device Interface Functions (DIFs)
"""
import copy
import logging as log
import subprocess
import xml.etree.ElementTree as ET
# Turn the Doxygen multi-file XML output into one giant XML file (and parse it
# into a python object), using the provided XLST file.
def get_combined_xml(doxygen_xml_path):
xsltproc_args = [
"xsltproc",
str(doxygen_xml_path.joinpath("combine.xslt")),
str(doxygen_xml_path.joinpath("index.xml")),
]
combined_xml_res = subprocess.run(
xsltproc_args, check=True,
cwd=str(doxygen_xml_path), stdout=subprocess.PIPE,
universal_newlines=True,
)
return ET.fromstring(combined_xml_res.stdout)
# Get all information about individual DIF functions that are specified in one
# DIF header. This returns only the Info from the XML that we require.
def get_difref_info(combined_xml, dif_header):
compound = _get_dif_file_compound(combined_xml, dif_header)
if compound is None:
return []
file_id = _get_dif_file_id(compound)
functions = _get_dif_function_info(compound, file_id)
return functions
# Create HTML List of DIFs, using the info from the combined xml
def gen_listing_html(html_path: str, combined_xml, dif_header, dif_listings_html):
compound = _get_dif_file_compound(combined_xml, dif_header)
if compound is None:
log.error("Doxygen output not found for {}".format(dif_header))
return
file_id = _get_dif_file_id(compound)
functions = _get_dif_function_info(html_path, compound, file_id)
if len(functions) == 0:
log.error("No DIF functions found for {}".format(dif_header))
return
# Generate DIF listing header
dif_listings_html.write('<p>To use this DIF, include the following C header:</p>')
dif_listings_html.write('<pre><code class=language-c data-lang=c>')
dif_listings_html.write('#include "<a href="{}/{}.html">{}</a>"'.format(
html_path, file_id, dif_header,
))
dif_listings_html.write('</code></pre>\n')
# Generate DIF function list.
dif_listings_html.write('<p>This header provides the following device interface functions:</p>')
dif_listings_html.write('<ul>\n')
for f in sorted(functions, key=lambda x: x['name']):
dif_listings_html.write('<li title="{prototype}" id="Dif_{name}">'.format(**f))
dif_listings_html.write('<a href="{full_url}">'.format(**f))
dif_listings_html.write('<code>{name}</code>'.format(**f))
dif_listings_html.write('</a>\n')
dif_listings_html.write(f['description'])
dif_listings_html.write('</li>\n')
dif_listings_html.write('</ul>\n')
# Generate HTML link for single function, using info returned from
# get_difref_info
def gen_difref_html(function_info, difref_html):
difref_html.write('<a href="{full_url}">'.format(**function_info))
difref_html.write('<code>{name}</code>'.format(**function_info))
difref_html.write('</a>\n')
def _get_dif_file_compound(combined_xml, dif_header):
for c in combined_xml.findall('compounddef[@kind="file"]'):
if c.find("location").attrib["file"] == dif_header:
return c
return None
def _get_dif_file_id(compound):
return compound.attrib["id"]
def _get_dif_function_info(html_path: str, compound, file_id):
funcs = compound.find('sectiondef[@kind="func"]')
if funcs is None:
return []
# Collect useful info on each function
functions = []
for m in funcs.findall('memberdef[@kind="function"]'):
func_id = m.attrib['id']
# Strip refid prefix, which is separated from the funcid by `_1`
if func_id.startswith(file_id + '_1'):
# The +2 here is because of the weird `_1` separator
func_id = func_id[len(file_id) + 2:]
else:
# I think this denotes that this function isn't from this file
continue
func_info = {}
func_info["id"] = m.attrib["id"]
func_info["file_id"] = file_id
func_info["local_id"] = func_id
func_info["full_url"] = "{}/{}.html#{}".format(html_path, file_id, func_id)
func_info["name"] = _get_text_or_empty(m, "name")
func_info["prototype"] = _get_text_or_empty(
m, "definition") + _get_text_or_empty(m, "argsstring")
func_info["description"] = _get_html_or_empty(m,
"briefdescription/para")
functions.append(func_info)
return functions
def _get_html_or_empty(element: ET.Element, xpath: str) -> str:
""" Get a minimal HTML-rendering of the children in an element.
element is expected to be a docCmdGroup according to the DoxyGen schema [1],
but only a very minimal subset of formatting is transferred to semantic
HTML. However, the tag structure is retained by transforming all tags into
HTML `span` tags with a class attribute `doxygentag-ORIGINALTAGNAME`. This
can be used to write CSS targeting specific Doxygen tags and recreate the
intended formatting.
In addtion, the following semantic transformations are performed:
- `computeroutput` is transformed to `code`
[1] https://github.com/doxygen/doxygen/blob/master/templates/xml/compound.xsd"""
inner = element.find(xpath)
# if the element isn't found, return ""
if inner is None:
return ""
# Avoid modifying the passed element argument.
inner_copy = copy.deepcopy(inner)
for c in inner_copy.iter():
c.set('class', 'doxygentag-' + c.tag)
if c.tag == 'computeroutput':
c.tag = 'code'
else:
c.tag = 'span'
# Create a string from all subelements
text = ET.tostring(inner_copy, encoding="unicode", method="html")
return text or ""
def _get_text_or_empty(element: ET.Element, xpath: str) -> str:
""" Get all text of an element, without any tags """
inner = element.find(xpath)
if inner is None:
return ""
return ' '.join([e for e in inner.itertext()]) or ""