| # 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 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 base in rnames: | 
 |             if match.group(1)[-1] == ".": | 
 |                 return ('<a href="#Reg_' + base + '"><code class=\"reg\">' + | 
 |                         match.group(1)[:-1] + '</code></a>.') | 
 |             else: | 
 |                 return ('<a href="#Reg_' + 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)) |