| import re | |
| from pathlib import Path | |
| from .generatorbase import GeneratorBase | |
| from .model import ( | |
| ReferenceManual, | |
| Function, | |
| Object, | |
| PosArg, | |
| VarArgs, | |
| Kwarg, | |
| ) | |
| import typing as T | |
| class ManPage: | |
| def __init__(self, path: Path): | |
| self.path = path | |
| self.text = "" | |
| def reset_font(self) -> None: | |
| self.text += ".P\n" | |
| def title(self, name: str, section: int) -> None: | |
| import datetime | |
| date = datetime.date.today() | |
| self.reset_font() | |
| self.text += f'.TH "{name}" "{section}" "{date}"\n' | |
| def section(self, name: str) -> None: | |
| self.reset_font() | |
| self.text += f".SH {name}\n" | |
| def subsection(self, name: str) -> None: | |
| self.reset_font() | |
| self.text += f".SS {name}\n" | |
| def par(self, text: str) -> None: | |
| self.reset_font() | |
| self.text += f"{text}\n" | |
| def indent(self, amount: int = 4) -> None: | |
| self.text += f".RS {amount}\n" | |
| def unindent(self) -> None: | |
| self.text += ".RE\n" | |
| def br(self) -> None: | |
| self.text += ".br\n" | |
| def nl(self) -> None: | |
| self.text += "\n" | |
| def line(self, text: str) -> None: | |
| if text and text[0] in [".", "'"]: | |
| self.text += "\\" | |
| self.text += f"{text}\n" | |
| def inline(self, text: str) -> None: | |
| self.text += f"{text}" | |
| def write(self) -> None: | |
| self.path.write_text(self.text, encoding="utf-8") | |
| @staticmethod | |
| def bold(text: str) -> str: | |
| return f"\\fB{text}\\fR" | |
| @staticmethod | |
| def italic(text: str) -> str: | |
| return f"\\fI{text}\\fR" | |
| class GeneratorMan(GeneratorBase): | |
| def __init__( | |
| self, manual: ReferenceManual, out: Path, enable_modules: bool | |
| ) -> None: | |
| super().__init__(manual) | |
| self.out = out | |
| self.enable_modules = enable_modules | |
| self.links: T.List[str] = [] | |
| def generate_description(self, page: ManPage, desc: str) -> None: | |
| def italicise(match: T.Match[str]) -> str: | |
| v = match.group(1) | |
| if v[0] == "@": | |
| v = v[1:] | |
| return ManPage.italic(v) | |
| desc = re.sub(re.compile(r"\[\[(.*?)\]\]", re.DOTALL), italicise, desc) | |
| def linkify(match: T.Match[str]) -> str: | |
| replacement = ManPage.italic(match.group(1)) | |
| if match.group(2)[0] != "#": | |
| if match.group(2) in self.links: | |
| num = self.links.index(match.group(2)) | |
| else: | |
| self.links.append(match.group(2)) | |
| num = len(self.links) | |
| replacement += f"[{num}]" | |
| return replacement | |
| desc = re.sub(re.compile(r"\[(.*?)\]\((.*?)\)", re.DOTALL), linkify, desc) | |
| def bold(match: T.Match[str]) -> str: | |
| return ManPage.bold(match.group(1)) | |
| desc = re.sub(re.compile(r"\*(.*?)\*"), bold, desc) | |
| isCode = False | |
| for chunk in desc.split("```"): | |
| if isCode: | |
| page.indent() | |
| lines = chunk.strip().split("\n") | |
| if lines[0] == "meson": | |
| lines = lines[1:] | |
| for line in lines: | |
| page.line(line) | |
| page.br() | |
| page.unindent() | |
| else: | |
| inList = False | |
| for line in chunk.strip().split("\n"): | |
| if len(line) == 0: | |
| page.nl() | |
| if inList: | |
| page.nl() | |
| inList = False | |
| elif line[0:2] in ["- ", "* "]: | |
| if inList: | |
| page.nl() | |
| page.br() | |
| else: | |
| inList = True | |
| page.inline(line.strip() + " ") | |
| elif inList and line[0] == " ": | |
| page.inline(line.strip() + " ") | |
| else: | |
| inList = False | |
| page.line(line) | |
| if inList: | |
| page.nl() | |
| isCode = not isCode | |
| def function_name(self, f: Function, o: Object = None) -> str: | |
| name = "" | |
| if o is not None: | |
| name += f"{o.name}." | |
| name += f.name | |
| return name | |
| def generate_function_signature( | |
| self, page: ManPage, f: Function, o: Object = None | |
| ) -> None: | |
| args = [] | |
| if f.posargs: | |
| args += [arg.name for arg in f.posargs] | |
| if f.varargs: | |
| args += [f.varargs.name + "..."] | |
| if f.optargs: | |
| args += [f"[{arg.name}]" for arg in f.optargs] | |
| for kwarg in self.sorted_and_filtered(list(f.kwargs.values())): | |
| kw = kwarg.name + ":" | |
| if kwarg.default: | |
| kw += " " + ManPage.bold(kwarg.default) | |
| args += [kw] | |
| ret = ManPage.italic(f.returns.raw) + " " | |
| prefix = f"{ret}{self.function_name(f, o)}(" | |
| sig = ", ".join(args) | |
| suffix = ")" | |
| if len(prefix) + len(sig) + len(suffix) > 70: | |
| page.line(prefix) | |
| page.br() | |
| page.indent() | |
| for arg in args: | |
| page.line(arg + ",") | |
| page.br() | |
| page.unindent() | |
| page.line(suffix) | |
| else: | |
| page.line(prefix + sig + suffix) | |
| def base_info( | |
| self, x: T.Union[PosArg, VarArgs, Kwarg, Function, Object] | |
| ) -> T.List[str]: | |
| info = [] | |
| if x.deprecated: | |
| info += [ManPage.bold("deprecated") + f" since {x.deprecated}"] | |
| if x.since: | |
| info += [f"since {x.since}"] | |
| return info | |
| def generate_function_arg( | |
| self, | |
| page: ManPage, | |
| arg: T.Union[PosArg, VarArgs, Kwarg], | |
| isOptarg: bool = False, | |
| ) -> None: | |
| required = ( | |
| arg.required | |
| if isinstance(arg, Kwarg) | |
| else not isOptarg and not isinstance(arg, VarArgs) | |
| ) | |
| page.line(ManPage.bold(arg.name)) | |
| info = [ManPage.italic(arg.type.raw)] | |
| if required: | |
| info += [ManPage.bold("required")] | |
| if isinstance(arg, (PosArg, Kwarg)) and arg.default: | |
| info += [f"default: {arg.default}"] | |
| if isinstance(arg, VarArgs): | |
| mn = 0 if arg.min_varargs < 0 else arg.min_varargs | |
| mx = "N" if arg.max_varargs < 0 else arg.max_varargs | |
| info += [f"{mn}...{mx} times"] | |
| info += self.base_info(arg) | |
| page.line(", ".join(info)) | |
| page.br() | |
| page.indent(2) | |
| self.generate_description(page, arg.description.strip()) | |
| page.unindent() | |
| page.nl() | |
| def generate_function_argument_section( | |
| self, | |
| page: ManPage, | |
| name: str, | |
| args: T.Sequence[T.Union[PosArg, VarArgs, Kwarg]], | |
| isOptarg: bool = False, | |
| ) -> None: | |
| if not args: | |
| return | |
| page.line(ManPage.bold(name)) | |
| page.indent() | |
| for arg in args: | |
| self.generate_function_arg(page, arg, isOptarg) | |
| page.unindent() | |
| def generate_sub_sub_section( | |
| self, page: ManPage, name: str, text: T.List[str], process: bool = True | |
| ) -> None: | |
| page.line(ManPage.bold(name)) | |
| page.indent() | |
| if process: | |
| for line in text: | |
| self.generate_description(page, line.strip()) | |
| else: | |
| page.line("\n\n".join([line.strip() for line in text])) | |
| page.unindent() | |
| def generate_function(self, page: ManPage, f: Function, obj: Object = None) -> None: | |
| page.subsection(self.function_name(f, obj) + "()") | |
| page.indent(0) | |
| page.line(ManPage.bold("SYNOPSIS")) | |
| page.indent() | |
| self.generate_function_signature(page, f, obj) | |
| info = self.base_info(f) | |
| if info: | |
| page.nl() | |
| page.line(", ".join(info)) | |
| page.unindent() | |
| page.nl() | |
| self.generate_sub_sub_section(page, "DESCRIPTION", [f.description]) | |
| page.nl() | |
| self.generate_function_argument_section(page, "POSARGS", f.posargs) | |
| if f.varargs: | |
| self.generate_function_argument_section(page, "VARARGS", [f.varargs]) | |
| self.generate_function_argument_section(page, "OPTARGS", f.optargs, True) | |
| self.generate_function_argument_section( | |
| page, "KWARGS", self.sorted_and_filtered(list(f.kwargs.values())) | |
| ) | |
| if f.notes: | |
| self.generate_sub_sub_section(page, "NOTES", f.notes) | |
| if f.warnings: | |
| self.generate_sub_sub_section(page, "WARNINGS", f.warnings) | |
| if f.example: | |
| self.generate_sub_sub_section(page, "EXAMPLE", [f.example]) | |
| page.unindent() | |
| def generate_object(self, page: ManPage, obj: Object) -> None: | |
| page.subsection(obj.name) | |
| page.indent(2) | |
| info = self.base_info(obj) | |
| if info: | |
| page.line(", ".join(info)) | |
| page.br() | |
| if obj.extends: | |
| page.line(ManPage.bold("extends: ") + obj.extends) | |
| page.br() | |
| ret = [x.name for x in self.sorted_and_filtered(obj.returned_by)] | |
| if ret: | |
| page.line(ManPage.bold("returned_by: ") + ", ".join(ret)) | |
| page.br() | |
| ext = [x.name for x in self.sorted_and_filtered(obj.extended_by)] | |
| if ext: | |
| page.line(ManPage.bold("extended_by: ") + ", ".join(ext)) | |
| page.br() | |
| page.nl() | |
| self.generate_description(page, obj.description.strip()) | |
| page.nl() | |
| if obj.notes: | |
| self.generate_sub_sub_section(page, "NOTES", obj.notes) | |
| if obj.warnings: | |
| self.generate_sub_sub_section(page, "WARNINGS", obj.warnings) | |
| if obj.example: | |
| self.generate_sub_sub_section(page, "EXAMPLE", [obj.example]) | |
| page.unindent() | |
| def generate(self) -> None: | |
| page = ManPage(self.out) | |
| page.title("meson-reference", 3) | |
| page.section("NAME") | |
| page.par( | |
| f"meson-reference v{self._extract_meson_version()}" | |
| + " - a reference for meson functions and objects" | |
| ) | |
| page.section("DESCRIPTION") | |
| self.generate_description( | |
| page, | |
| """This manual is divided into two sections, *FUNCTIONS* and *OBJECTS*. *FUNCTIONS* contains a reference for all meson functions and methods. Methods are denoted by [[object_name]].[[method_name]](). *OBJECTS* contains additional information about each object.""", | |
| ) | |
| page.section("FUNCTIONS") | |
| for f in self.sorted_and_filtered(self.functions): | |
| self.generate_function(page, f) | |
| for obj in self.sorted_and_filtered(self.objects): | |
| for f in self.sorted_and_filtered(obj.methods): | |
| self.generate_function(page, f, obj) | |
| page.section("OBJECTS") | |
| for obj in self.sorted_and_filtered(self.objects): | |
| self.generate_object(page, obj) | |
| page.section("SEE ALSO") | |
| for i in range(len(self.links)): | |
| link = self.links[i] | |
| page.line(f"[{i + 1}] {link}") | |
| page.br() | |
| page.write() |