| 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() |