blob: b55ea4c2a82b2fc72519c78072b4186bf9741df7 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import logging as log
import re
from typing import List, Match, Optional, Set
def get_reg_link(rname: str) -> str:
'''Return regname with a HTML link to itself'''
return '<a href="#{}">{}</a>'.format(rname.lower(), rname)
def expand_paras(s: str, rnames: Set[str]) -> List[str]:
'''Expand a description field to HTML.
This supports a sort of simple pseudo-markdown. Supported Markdown
features:
- Separate paragraphs on a blank line
- **bold** and *italicised* text
- Back-ticks for pre-formatted text
We also generate links to registers when a name is prefixed with a double
exclamation mark. For example, if there is a register FOO then !!FOO or
!!FOO.field will generate a link to that register.
Returns a list of rendered paragraphs
'''
# Start by splitting into paragraphs. The regex matches a newline followed
# by one or more lines that just contain whitespace. Then render each
# paragraph with the _expand_paragraph worker function.
paras = [_expand_paragraph(paragraph.strip(), rnames)
for paragraph in re.split(r'\n(?:\s*\n)+', s)]
# There will always be at least one paragraph (splitting an empty string
# gives [''])
assert paras
return paras
def _expand_paragraph(s: str, rnames: Set[str]) -> str:
'''Expand a single paragraph, as described in _get_desc_paras'''
def fieldsub(match: Match[str]) -> str:
base = match.group(1).partition('.')[0].lower()
# If we do not find the register name, there is a chance that this
# is a multireg that spans more than one register entry.
# We check whether at least _0 and _1 exist (via a set intersection),
# and still insert the link if these names exist.
# Note that we do not have to modify the link name since we insert
# a link target without the index suffix right before the first multireg
# entry in the register table.
mr_names = set([base + "_0", base + "_1"])
if base in rnames or len(rnames & mr_names) == 2:
if match.group(1)[-1] == ".":
return ('<a href="#' + base + '"><code class=\"reg\">' +
match.group(1)[:-1] + '</code></a>.')
else:
return ('<a href="#' + base + '"><code class=\"reg\">' +
match.group(1) + '</code></a>')
log.warn('!!' + match.group(1).partition('.')[0] +
' not found in register list.')
return match.group(0)
# Split out pre-formatted text. Because the call to re.split has a capture
# group in the regex, we get an odd number of results. Elements with even
# indices are "normal text". Those with odd indices are the captured text
# between the back-ticks.
code_split = re.split(r'`([^`]+)`', s)
expanded_parts = []
for idx, part in enumerate(code_split):
if idx & 1:
# Text contained in back ticks
expanded_parts.append('<code>{}</code>'.format(part))
continue
part = re.sub(r"!!([A-Za-z0-9_.]+)", fieldsub, part)
part = re.sub(r"(?s)\*\*(.+?)\*\*", r'<B>\1</B>', part)
part = re.sub(r"\*([^*]+?)\*", r'<I>\1</I>', part)
expanded_parts.append(part)
return '<p>{}</p>'.format(''.join(expanded_parts))
def render_td(s: str, rnames: Set[str], td_class: Optional[str]) -> str:
'''Expand a description field and put it in a <td>.
Returns a string. See _get_desc_paras for the format that gets expanded.
'''
desc_paras = expand_paras(s, rnames)
class_attr = '' if td_class is None else ' class="{}"'.format(td_class)
return '<td{}>{}</td>'.format(class_attr, ''.join(desc_paras))