aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2022-01-07 18:55:07 +0200
committerGitHub <noreply@github.com>2022-01-07 18:55:07 +0200
commit8b54ee58efe5dc8f2cb23a1e54578090243bc599 (patch)
tree6ec94ebe2dfe402d22296ced40bb81789c6fb4d4
parent484a389bbe186120f3328b6c47c86b087f32a530 (diff)
parent2c4a98f851c4a363cb8dd83b2270243ce2990442 (diff)
downloadmeson-8b54ee58efe5dc8f2cb23a1e54578090243bc599.zip
meson-8b54ee58efe5dc8f2cb23a1e54578090243bc599.tar.gz
meson-8b54ee58efe5dc8f2cb23a1e54578090243bc599.tar.bz2
Merge pull request #9773 from annacrombie/master
Add a man page backend to refman
-rw-r--r--.github/workflows/website.yml7
-rw-r--r--docs/markdown/snippets/refman_man_page_backend.md4
-rw-r--r--docs/meson.build16
-rw-r--r--docs/refman/generatorbase.py5
-rw-r--r--docs/refman/generatorjson.py7
-rw-r--r--docs/refman/generatorman.py382
-rw-r--r--docs/refman/main.py4
7 files changed, 417 insertions, 8 deletions
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index a71ee33..e3d3821 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -61,4 +61,11 @@ jobs:
file: docs/_build/reference_manual.json
tag: ${{ github.ref }}
if: ${{ github.event_name == 'release' }}
+ - name: Release the current man docs
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: docs/_build/meson-reference.3
+ tag: ${{ github.ref }}
+ if: ${{ github.event_name == 'release' }}
diff --git a/docs/markdown/snippets/refman_man_page_backend.md b/docs/markdown/snippets/refman_man_page_backend.md
new file mode 100644
index 0000000..2a774e2
--- /dev/null
+++ b/docs/markdown/snippets/refman_man_page_backend.md
@@ -0,0 +1,4 @@
+## Add a man page backend to refman
+
+The refman docs (function and object reference) can now be generated as a man
+page.
diff --git a/docs/meson.build b/docs/meson.build
index 53d4392..00e90ed 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -63,6 +63,22 @@ refman_json = custom_target(
],
)
+refman_man = custom_target(
+ 'gen_refman_man',
+ build_by_default: true,
+ input: refman_binary,
+ output: 'meson-reference.3',
+ command: [
+ genrefman,
+ '-l', 'pickle',
+ '-g', 'man',
+ '-i', '@INPUT@',
+ '-o', '@OUTPUT@',
+ '--force-color',
+ '--no-modules',
+ ],
+)
+
test('validate_docs', find_program('./jsonvalidator.py'), args: [refman_json])
hotdoc_prog = find_program('hotdoc', version: '>=0.13.7')
diff --git a/docs/refman/generatorbase.py b/docs/refman/generatorbase.py
index e93166f..08ce492 100644
--- a/docs/refman/generatorbase.py
+++ b/docs/refman/generatorbase.py
@@ -43,6 +43,11 @@ class GeneratorBase(metaclass=ABCMeta):
return f'0_{fn.name}'
return sorted([x for x in raw if not x.hidden], key=key_fn)
+ @staticmethod
+ def _extract_meson_version() -> str:
+ from mesonbuild.coredata import version
+ return version
+
@property
def functions(self) -> T.List[Function]:
return GeneratorBase.sorted_and_filtered(self.manual.functions)
diff --git a/docs/refman/generatorjson.py b/docs/refman/generatorjson.py
index f5164d4..9f92342 100644
--- a/docs/refman/generatorjson.py
+++ b/docs/refman/generatorjson.py
@@ -92,13 +92,6 @@ class GeneratorJSON(GeneratorBase):
'methods': {x.name: self._generate_function(x) for x in self.sorted_and_filtered(obj.methods)},
}
- def _extract_meson_version(self) -> str:
- # Hack around python relative imports to get to the Meson version
- import sys
- sys.path.append(Path(__file__).resolve().parents[2].as_posix())
- from mesonbuild.coredata import version
- return version
-
def generate(self) -> None:
data: J.Root = {
'version_major': J.VERSION_MAJOR,
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]()