The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
382 lines
11 KiB
382 lines
11 KiB
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()
|
|
|