0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-20 17:10:46 +03:00

nixos-render-docs: move recursive manual parsing to base class

the html renderer will need all of these functions as well. some
extensions will be needed, but we'll add those as they become necessary.
This commit is contained in:
pennae 2023-02-15 12:57:32 +01:00
parent 5b8be28e66
commit 2ab8e742a5

View file

@ -4,16 +4,90 @@ import json
from abc import abstractmethod
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any, cast, NamedTuple, Optional, Union
from typing import Any, cast, Generic, NamedTuple, Optional, Union
from xml.sax.saxutils import escape, quoteattr
import markdown_it
from markdown_it.token import Token
from . import options
from . import md, options
from .docbook import DocBookRenderer, Heading
from .md import Converter
class BaseConverter(Converter[md.TR], Generic[md.TR]):
_base_paths: list[Path]
def convert(self, file: Path) -> str:
self._base_paths = [ file ]
try:
with open(file, 'r') as f:
return self._render(f.read())
except Exception as e:
raise RuntimeError(f"failed to render manual {file}") from e
def _parse(self, src: str) -> list[Token]:
tokens = super()._parse(src)
for token in tokens:
if token.type != "fence" or not token.info.startswith("{=include=} "):
continue
typ = token.info[12:].strip()
if typ == 'options':
token.type = 'included_options'
self._parse_options(token)
elif typ in [ 'sections', 'chapters', 'preface', 'parts', 'appendix' ]:
token.type = 'included_' + typ
self._parse_included_blocks(token)
else:
raise RuntimeError(f"unsupported structural include type '{typ}'")
return tokens
def _parse_included_blocks(self, token: Token) -> None:
assert token.map
included = token.meta['included'] = []
for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2):
line = line.strip()
path = self._base_paths[-1].parent / line
if path in self._base_paths:
raise RuntimeError(f"circular include found in line {lnum}")
try:
self._base_paths.append(path)
with open(path, 'r') as f:
tokens = self._parse(f.read())
included.append((tokens, path))
self._base_paths.pop()
except Exception as e:
raise RuntimeError(f"processing included file {path} from line {lnum}") from e
def _parse_options(self, token: Token) -> None:
assert token.map
items = {}
for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2):
if len(args := line.split(":", 1)) != 2:
raise RuntimeError(f"options directive with no argument in line {lnum}")
(k, v) = (args[0].strip(), args[1].strip())
if k in items:
raise RuntimeError(f"duplicate options directive {k} in line {lnum}")
items[k] = v
try:
id_prefix = items.pop('id-prefix')
varlist_id = items.pop('list-id')
source = items.pop('source')
except KeyError as e:
raise RuntimeError(f"options directive {e} missing in block at line {token.map[0] + 1}")
if items.keys():
raise RuntimeError(
f"unsupported options directives in block at line {token.map[0] + 1}",
" ".join(items.keys()))
try:
with open(self._base_paths[-1].parent / source, 'r') as f:
token.meta['id-prefix'] = id_prefix
token.meta['list-id'] = varlist_id
token.meta['source'] = json.load(f)
except Exception as e:
raise RuntimeError(f"processing options block in line {token.map[0] + 1}") from e
class ManualDocBookRenderer(DocBookRenderer):
_toplevel_tag: str
_revision: str
@ -113,84 +187,11 @@ class ManualDocBookRenderer(DocBookRenderer):
info = f" language={quoteattr(token.info)}" if token.info != "" else ""
return f"<programlisting{info}>\n{escape(token.content)}</programlisting>"
class DocBookConverter(Converter[ManualDocBookRenderer]):
_base_paths: list[Path]
class DocBookConverter(BaseConverter[ManualDocBookRenderer]):
def __init__(self, manpage_urls: Mapping[str, str], revision: str):
super().__init__()
self._renderer = ManualDocBookRenderer('book', revision, manpage_urls)
def convert(self, file: Path) -> str:
self._base_paths = [ file ]
try:
with open(file, 'r') as f:
return self._render(f.read())
except Exception as e:
raise RuntimeError(f"failed to render manual {file}") from e
def _parse(self, src: str) -> list[Token]:
tokens = super()._parse(src)
for token in tokens:
if token.type != "fence" or not token.info.startswith("{=include=} "):
continue
typ = token.info[12:].strip()
if typ == 'options':
token.type = 'included_options'
self._parse_options(token)
elif typ in [ 'sections', 'chapters', 'preface', 'parts', 'appendix' ]:
token.type = 'included_' + typ
self._parse_included_blocks(token)
else:
raise RuntimeError(f"unsupported structural include type '{typ}'")
return tokens
def _parse_included_blocks(self, token: Token) -> None:
assert token.map
included = token.meta['included'] = []
for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2):
line = line.strip()
path = self._base_paths[-1].parent / line
if path in self._base_paths:
raise RuntimeError(f"circular include found in line {lnum}")
try:
self._base_paths.append(path)
with open(path, 'r') as f:
tokens = self._parse(f.read())
included.append((tokens, path))
self._base_paths.pop()
except Exception as e:
raise RuntimeError(f"processing included file {path} from line {lnum}") from e
def _parse_options(self, token: Token) -> None:
assert token.map
items = {}
for (lnum, line) in enumerate(token.content.splitlines(), token.map[0] + 2):
if len(args := line.split(":", 1)) != 2:
raise RuntimeError(f"options directive with no argument in line {lnum}")
(k, v) = (args[0].strip(), args[1].strip())
if k in items:
raise RuntimeError(f"duplicate options directive {k} in line {lnum}")
items[k] = v
try:
id_prefix = items.pop('id-prefix')
varlist_id = items.pop('list-id')
source = items.pop('source')
except KeyError as e:
raise RuntimeError(f"options directive {e} missing in block at line {token.map[0] + 1}")
if items.keys():
raise RuntimeError(
f"unsupported options directives in block at line {token.map[0] + 1}",
" ".join(items.keys()))
try:
with open(self._base_paths[-1].parent / source, 'r') as f:
token.meta['id-prefix'] = id_prefix
token.meta['list-id'] = varlist_id
token.meta['source'] = json.load(f)
except Exception as e:
raise RuntimeError(f"processing options block in line {token.map[0] + 1}") from e
def _build_cli_db(p: argparse.ArgumentParser) -> None: