forked from mirrors/nixpkgs
nixos-render-docs: improve error messages for multi-title manual chapters
This commit is contained in:
parent
c95374a887
commit
9711de7b7e
|
@ -1,12 +1,38 @@
|
|||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
import traceback
|
||||
from io import StringIO
|
||||
from pprint import pprint
|
||||
from typing import Any, Dict
|
||||
|
||||
from .md import Converter
|
||||
from . import manual
|
||||
from . import options
|
||||
|
||||
def pretty_print_exc(e: BaseException, *, _desc_text: str = "error") -> None:
|
||||
print(f"\x1b[1;31m{_desc_text}:\x1b[0m", file=sys.stderr)
|
||||
# destructure Exception and RuntimeError specifically so we can show nice
|
||||
# messages for errors that weren't given their own exception type with
|
||||
# a good pretty-printer.
|
||||
if type(e) is Exception or type(e) is RuntimeError:
|
||||
args = e.args
|
||||
if len(args) and isinstance(args[0], str):
|
||||
print("\t", args[0], file=sys.stderr, sep="")
|
||||
args = args[1:]
|
||||
buf = StringIO()
|
||||
for arg in args:
|
||||
pprint(arg, stream=buf)
|
||||
if extra_info := buf.getvalue():
|
||||
print(f"\x1b[1;34mextra info:\x1b[0m", file=sys.stderr)
|
||||
print(textwrap.indent(extra_info, "\t"), file=sys.stderr, end="")
|
||||
else:
|
||||
print(e)
|
||||
if e.__context__ is not None:
|
||||
print("", file=sys.stderr)
|
||||
pretty_print_exc(e.__context__, _desc_text="caused by")
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description='render nixos manual bits')
|
||||
|
||||
|
@ -16,9 +42,14 @@ def main() -> None:
|
|||
manual.build_cli(commands.add_parser('manual'))
|
||||
|
||||
args = parser.parse_args()
|
||||
if args.command == 'options':
|
||||
options.run_cli(args)
|
||||
elif args.command == 'manual':
|
||||
manual.run_cli(args)
|
||||
else:
|
||||
raise RuntimeError('command not hooked up', args)
|
||||
try:
|
||||
if args.command == 'options':
|
||||
options.run_cli(args)
|
||||
elif args.command == 'manual':
|
||||
manual.run_cli(args)
|
||||
else:
|
||||
raise RuntimeError('command not hooked up', args)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
pretty_print_exc(e)
|
||||
sys.exit(1)
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
|
||||
from abc import abstractmethod
|
||||
from collections.abc import MutableMapping, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Any, cast, NamedTuple, Optional, Union
|
||||
from xml.sax.saxutils import escape, quoteattr
|
||||
from markdown_it.token import Token
|
||||
|
@ -26,11 +27,15 @@ class BaseConverter(Converter):
|
|||
super().__init__(manpage_urls)
|
||||
self._sections = []
|
||||
|
||||
def add_section(self, id: Optional[str], chapters: list[str]) -> None:
|
||||
def add_section(self, id: Optional[str], chapters: list[Path]) -> None:
|
||||
self._sections.append(RenderedSection(id))
|
||||
for content in chapters:
|
||||
self._md.renderer._title_seen = False # type: ignore[attr-defined]
|
||||
self._sections[-1].chapters.append(self._render(content))
|
||||
for chpath in chapters:
|
||||
try:
|
||||
with open(chpath, 'r') as f:
|
||||
self._md.renderer._title_seen = False # type: ignore[attr-defined]
|
||||
self._sections[-1].chapters.append(self._render(f.read()))
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"failed to render manual chapter {chpath}") from e
|
||||
|
||||
@abstractmethod
|
||||
def finalize(self) -> str: raise NotImplementedError()
|
||||
|
@ -45,7 +50,13 @@ class ManualDocBookRenderer(DocBookRenderer):
|
|||
(tag, attrs) = super()._heading_tag(token, tokens, i, options, env)
|
||||
if self._title_seen:
|
||||
if token.tag == 'h1':
|
||||
raise RuntimeError("only one title heading allowed", token)
|
||||
assert token.map is not None
|
||||
raise RuntimeError(
|
||||
"only one title heading (# [text...]) allowed per manual chapter "
|
||||
f"but found a second in lines [{token.map[0]}..{token.map[1]}]. "
|
||||
"please remove all such headings except the first, split your "
|
||||
"chapters, or demote the subsequent headings to (##) or lower.",
|
||||
token)
|
||||
return (tag, attrs)
|
||||
self._title_seen = True
|
||||
return ("chapter", attrs | {
|
||||
|
@ -111,7 +122,7 @@ class ChaptersAction(argparse.Action):
|
|||
values: Union[str, Sequence[Any], None], opt_str: Optional[str] = None) -> None:
|
||||
sections = getattr(ns, self.dest)
|
||||
if sections is None: raise argparse.ArgumentError(self, "no active section")
|
||||
sections[-1].chapters.extend(cast(Sequence[str], values))
|
||||
sections[-1].chapters.extend(map(Path, cast(Sequence[str], values)))
|
||||
|
||||
def _build_cli_db(p: argparse.ArgumentParser) -> None:
|
||||
p.add_argument('--manpage-urls', required=True)
|
||||
|
@ -124,11 +135,7 @@ def _run_cli_db(args: argparse.Namespace) -> None:
|
|||
with open(args.manpage_urls, 'r') as manpage_urls:
|
||||
md = DocBookConverter(json.load(manpage_urls))
|
||||
for section in args.contents:
|
||||
chapters = []
|
||||
for p in section.chapters:
|
||||
with open(p, 'r') as f:
|
||||
chapters.append(f.read())
|
||||
md.add_section(section.id, chapters)
|
||||
md.add_section(section.id, section.chapters)
|
||||
with open(args.outfile, 'w') as f:
|
||||
f.write(md.finalize())
|
||||
|
||||
|
|
Loading…
Reference in a new issue