from collections.abc import Mapping, Sequence from typing import cast, Optional, NamedTuple from html import escape from markdown_it.token import Token from .manual_structure import XrefTarget from .md import Renderer class UnresolvedXrefError(Exception): pass class Heading(NamedTuple): container_tag: str level: int html_tag: str # special handling for part content: whether partinfo div was already closed from # elsewhere or still needs closing. partintro_closed: bool # tocs are generated when the heading opens, but have to be emitted into the file # after the heading titlepage (and maybe partinfo) has been closed. toc_fragment: str _bullet_list_styles = [ 'disc', 'circle', 'square' ] _ordered_list_styles = [ '1', 'a', 'i', 'A', 'I' ] class HTMLRenderer(Renderer): _xref_targets: Mapping[str, XrefTarget] _headings: list[Heading] _attrspans: list[str] _hlevel_offset: int = 0 _bullet_list_nesting: int = 0 _ordered_list_nesting: int = 0 def __init__(self, manpage_urls: Mapping[str, str], xref_targets: Mapping[str, XrefTarget]): super().__init__(manpage_urls) self._headings = [] self._attrspans = [] self._xref_targets = xref_targets def render(self, tokens: Sequence[Token]) -> str: result = super().render(tokens) result += self._close_headings(None) return result def _pull_image(self, path: str) -> str: raise NotImplementedError() def text(self, token: Token, tokens: Sequence[Token], i: int) -> str: return escape(token.content) def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: return "
" def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: return "
" def hardbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str: return "{escape(token.content)}
'
def code_block(self, token: Token, tokens: Sequence[Token], i: int) -> str:
return self.fence(token, tokens, i)
def link_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
href = escape(cast(str, token.attrs['href']), True)
tag, title, target, text = "link", "", 'target="_top"', ""
if href.startswith('#'):
if not (xref := self._xref_targets.get(href[1:])):
raise UnresolvedXrefError(f"bad local reference, id {href} not known")
if tokens[i + 1].type == 'link_close':
tag, text = "xref", xref.title_html
if xref.title:
# titles are not attribute-safe on their own, so we need to replace quotes.
title = 'title="{}"'.format(xref.title.replace('"', '"'))
target, href = "", xref.href()
return f'{text}'
def link_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
return ""
def list_item_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
return '\n{escape(token.content)}' def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: return '
' def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: return "
{escape(token.content)}
'
if token.meta['name'] == 'var':
return f'{escape(token.content)}
'
if token.meta['name'] == 'env':
return f'{escape(token.content)}
'
if token.meta['name'] == 'option':
return f'{escape(token.content)}
'
if token.meta['name'] == 'manpage':
[page, section] = [ s.strip() for s in token.content.rsplit('(', 1) ]
section = section[:-1]
man = f"{page}({section})"
title = f'{escape(page)}'
vol = f"({escape(section)})"
ref = f'{title}{vol}'
if man in self._manpage_urls:
return f'{ref}'
else:
return ref
return super().myst_role(token, tokens, i)
def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int) -> str:
# we currently support *only* inline anchors and the special .keycap class to produce
# keycap-styled spans.
(id_part, class_part) = ("", "")
if s := token.attrs.get('id'):
id_part = f''
if s := token.attrs.get('class'):
if s == 'keycap':
class_part = ''
self._attrspans.append("")
else:
return super().attr_span_begin(token, tokens, i)
else:
self._attrspans.append("")
return id_part + class_part
def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int) -> str:
return self._attrspans.pop()
def heading_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
hlevel = int(token.tag[1:])
htag, hstyle = self._make_hN(hlevel)
if hstyle:
hstyle = f'style="{escape(hstyle, True)}"'
if anchor := cast(str, token.attrs.get('id', '')):
anchor = f''
result = self._close_headings(hlevel)
tag = self._heading_tag(token, tokens, i)
toc_fragment = self._build_toc(tokens, i)
self._headings.append(Heading(tag, hlevel, htag, tag != 'part', toc_fragment))
return (
f'{result}'
f'' def example_title_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: return '
' ' ' ) def figure_title_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: return ( ' ' '
' '