aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStone Tickle <lattis@mochiro.moe>2022-01-05 11:32:23 -0600
committerStone Tickle <lattis@mochiro.moe>2022-01-06 20:14:29 -0600
commitac468a139485022e11572fcde38daee1958d8be2 (patch)
tree0a3be1614b801f39bfb69446febd40259c90ee45
parent95057340b35288f9795109a477421825ba86f891 (diff)
downloadmeson-ac468a139485022e11572fcde38daee1958d8be2.zip
meson-ac468a139485022e11572fcde38daee1958d8be2.tar.gz
meson-ac468a139485022e11572fcde38daee1958d8be2.tar.bz2
add man page backend to refman
-rw-r--r--docs/refman/generatorman.py382
-rw-r--r--docs/refman/main.py4
2 files changed, 385 insertions, 1 deletions
diff --git a/docs/refman/generatorman.py b/docs/refman/generatorman.py
new file mode 100644
index 0000000..dfd211f
--- /dev/null
+++ b/docs/refman/generatorman.py
@@ -0,0 +1,382 @@
+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()
diff --git a/docs/refman/main.py b/docs/refman/main.py
index 5bc40f7..e2654c9 100644
--- a/docs/refman/main.py
+++ b/docs/refman/main.py
@@ -27,13 +27,14 @@ from .generatorjson import GeneratorJSON
from .generatorprint import GeneratorPrint
from .generatorpickle import GeneratorPickle
from .generatormd import GeneratorMD
+from .generatorman import GeneratorMan
meson_root = Path(__file__).absolute().parents[2]
def main() -> int:
parser = argparse.ArgumentParser(description='Meson reference manual generator')
parser.add_argument('-l', '--loader', type=str, default='yaml', choices=['yaml', 'pickle'], help='Information loader backend')
- parser.add_argument('-g', '--generator', type=str, choices=['print', 'pickle', 'md', 'json'], required=True, help='Generator backend')
+ parser.add_argument('-g', '--generator', type=str, choices=['print', 'pickle', 'md', 'json', 'man'], required=True, help='Generator backend')
parser.add_argument('-s', '--sitemap', type=Path, default=meson_root / 'docs' / 'sitemap.txt', help='Path to the input sitemap.txt')
parser.add_argument('-o', '--out', type=Path, required=True, help='Output directory for generated files')
parser.add_argument('-i', '--input', type=Path, default=meson_root / 'docs' / 'yaml', help='Input path for the selected loader')
@@ -59,6 +60,7 @@ def main() -> int:
'pickle': lambda: GeneratorPickle(refMan, args.out),
'md': lambda: GeneratorMD(refMan, args.out, args.sitemap, args.link_defs, not args.no_modules),
'json': lambda: GeneratorJSON(refMan, args.out, not args.no_modules),
+ 'man': lambda: GeneratorMan(refMan, args.out, not args.no_modules),
}
generator = generators[args.generator]()