aboutsummaryrefslogtreecommitdiff
path: root/docs/refman
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2021-10-04 22:19:07 +0300
committerGitHub <noreply@github.com>2021-10-04 22:19:07 +0300
commit2d65472c725f18b343aee00bf91b9ac98c08b95f (patch)
tree530a0d6ffee4ee96e875302cbeba660c93056d41 /docs/refman
parent75dd9fb67f793c687fa45744f3b276e35c87ca09 (diff)
parentb672ebca886dd6dc9b0f775eb769764750fd302c (diff)
downloadmeson-2d65472c725f18b343aee00bf91b9ac98c08b95f.zip
meson-2d65472c725f18b343aee00bf91b9ac98c08b95f.tar.gz
meson-2d65472c725f18b343aee00bf91b9ac98c08b95f.tar.bz2
Merge pull request #8960 from mensinda/yamlDoc
Reference Manual 2.0
Diffstat (limited to 'docs/refman')
-rw-r--r--docs/refman/__init__.py0
-rw-r--r--docs/refman/generatorbase.py71
-rw-r--r--docs/refman/generatormd.py386
-rw-r--r--docs/refman/generatorpickle.py26
-rw-r--r--docs/refman/generatorprint.py87
-rw-r--r--docs/refman/loaderbase.py216
-rw-r--r--docs/refman/loaderyaml.py203
-rw-r--r--docs/refman/main.py75
-rw-r--r--docs/refman/model.py113
-rw-r--r--docs/refman/templates/args.mustache28
-rw-r--r--docs/refman/templates/dummy.mustache8
-rw-r--r--docs/refman/templates/func.mustache55
-rw-r--r--docs/refman/templates/notes.mustache14
-rw-r--r--docs/refman/templates/object.mustache62
-rw-r--r--docs/refman/templates/root.functions.mustache20
-rw-r--r--docs/refman/templates/root.mustache51
-rw-r--r--docs/refman/templates/root_link.mustache1
-rw-r--r--docs/refman/templates/taggs.mustache18
18 files changed, 1434 insertions, 0 deletions
diff --git a/docs/refman/__init__.py b/docs/refman/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/refman/__init__.py
diff --git a/docs/refman/generatorbase.py b/docs/refman/generatorbase.py
new file mode 100644
index 0000000..e93166f
--- /dev/null
+++ b/docs/refman/generatorbase.py
@@ -0,0 +1,71 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABCMeta, abstractmethod
+import typing as T
+
+from .model import ReferenceManual, Function, Method, Object, ObjectType, NamedObject
+
+_N = T.TypeVar('_N', bound=NamedObject)
+
+class GeneratorBase(metaclass=ABCMeta):
+ def __init__(self, manual: ReferenceManual) -> None:
+ self.manual = manual
+
+ @abstractmethod
+ def generate(self) -> None:
+ pass
+
+ @staticmethod
+ def brief(raw: _N) -> str:
+ desc_lines = raw.description.split('\n')
+ brief = desc_lines[0]
+ if '.' in brief and '[[' not in brief:
+ brief = brief[:brief.index('.')]
+ return brief.strip()
+
+ @staticmethod
+ def sorted_and_filtered(raw: T.List[_N]) -> T.List[_N]:
+ def key_fn(fn: NamedObject) -> str:
+ if isinstance(fn, Method):
+ return f'1_{fn.obj.name}.{fn.name}'
+ return f'0_{fn.name}'
+ return sorted([x for x in raw if not x.hidden], key=key_fn)
+
+ @property
+ def functions(self) -> T.List[Function]:
+ return GeneratorBase.sorted_and_filtered(self.manual.functions)
+
+ @property
+ def objects(self) -> T.List[Object]:
+ return GeneratorBase.sorted_and_filtered(self.manual.objects)
+
+ @property
+ def elementary(self) -> T.List[Object]:
+ return [x for x in self.objects if x.obj_type == ObjectType.ELEMENTARY]
+
+ @property
+ def builtins(self) -> T.List[Object]:
+ return [x for x in self.objects if x.obj_type == ObjectType.BUILTIN]
+
+ @property
+ def returned(self) -> T.List[Object]:
+ return [x for x in self.objects if x.obj_type == ObjectType.RETURNED and x.defined_by_module is None]
+
+ @property
+ def modules(self) -> T.List[Object]:
+ return [x for x in self.objects if x.obj_type == ObjectType.MODULE]
+
+ def extract_returned_by_module(self, module: Object) -> T.List[Object]:
+ return [x for x in self.objects if x.obj_type == ObjectType.RETURNED and x.defined_by_module is module]
diff --git a/docs/refman/generatormd.py b/docs/refman/generatormd.py
new file mode 100644
index 0000000..abf8a99
--- /dev/null
+++ b/docs/refman/generatormd.py
@@ -0,0 +1,386 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .generatorbase import GeneratorBase
+import re
+import json
+
+from .model import (
+ ReferenceManual,
+ Function,
+ Method,
+ Object,
+ ObjectType,
+ Type,
+ DataTypeInfo,
+ ArgBase,
+ PosArg,
+ VarArgs,
+ Kwarg,
+)
+
+from pathlib import Path
+from textwrap import dedent
+import typing as T
+
+from mesonbuild import mlog
+
+PlaceholderTypes = T.Union[None, str, bool]
+FunctionDictType = T.Dict[
+ str,
+ T.Union[
+ PlaceholderTypes,
+ T.Dict[str, PlaceholderTypes],
+ T.Dict[str, T.Dict[str, PlaceholderTypes]],
+ T.Dict[str, T.List[T.Dict[str, PlaceholderTypes]]],
+ T.List[T.Dict[str, PlaceholderTypes]],
+ T.List[str],
+ ]
+]
+
+_ROOT_BASENAME = 'RefMan'
+
+_OBJ_ID_MAP = {
+ ObjectType.ELEMENTARY: 'elementary',
+ ObjectType.BUILTIN: 'builtin',
+ ObjectType.MODULE: 'module',
+ ObjectType.RETURNED: 'returned',
+}
+
+# Indent all but the first line with 4*depth spaces.
+# This function is designed to be used with `dedent`
+# and fstrings where multiline strings are used during
+# the string interpolation.
+def smart_indent(raw: str, depth: int = 3) -> str:
+ lines = raw.split('\n')
+ first_line = lines[0]
+ lines = [' ' * (4 * depth) + x for x in lines]
+ lines[0] = first_line # Do not indent the first line
+ return '\n'.join(lines)
+
+def code_block(code: str) -> str:
+ code = dedent(code)
+ return f'<pre><code class="language-meson">{code}</code></pre>'
+
+class GeneratorMD(GeneratorBase):
+ def __init__(self, manual: ReferenceManual, sitemap_out: Path, sitemap_in: Path, link_def_out: Path) -> None:
+ super().__init__(manual)
+ self.sitemap_out = sitemap_out.resolve()
+ self.sitemap_in = sitemap_in.resolve()
+ self.link_def_out = link_def_out.resolve()
+ self.out_dir = self.sitemap_out.parent
+ self.generated_files: T.Dict[str, str] = {}
+
+ # Utility functions
+ def _gen_filename(self, file_id: str, *, extension: str = 'md') -> str:
+ parts = file_id.split('.')
+ assert parts[0] == 'root'
+ assert all([x for x in parts])
+ parts[0] = _ROOT_BASENAME
+ parts = [re.sub(r'[0-9]+_', '', x) for x in parts]
+ return f'{"_".join(parts)}.{extension}'
+
+ def _gen_object_file_id(self, obj: Object) -> str:
+ '''
+ Deterministically generate a unique file ID for the Object.
+
+ This ID determines where the object will be inserted in the sitemap.
+ '''
+ if obj.obj_type == ObjectType.RETURNED and obj.defined_by_module is not None:
+ base = self._gen_object_file_id(obj.defined_by_module)
+ return f'{base}.{obj.name}'
+ return f'root.{_OBJ_ID_MAP[obj.obj_type]}.{obj.name}'
+
+ def _link_to_object(self, obj: T.Union[Function, Object], in_code_block: bool = False) -> str:
+ '''
+ Generate a palaceholder tag for the the function/method/object documentation.
+ This tag is then replaced in the custom hotdoc plugin.
+ '''
+ prefix = '#' if in_code_block else ''
+ if isinstance(obj, Object):
+ return f'[[{prefix}@{obj.name}]]'
+ elif isinstance(obj, Method):
+ return f'[[{prefix}{obj.obj.name}.{obj.name}]]'
+ elif isinstance(obj, Function):
+ return f'[[{prefix}{obj.name}]]'
+ else:
+ raise RuntimeError(f'Invalid argument {obj}')
+
+ def _write_file(self, data: str, file_id: str) -> None:#
+ ''' Write the data to disk ans store the id for the generated data '''
+
+ self.generated_files[file_id] = self._gen_filename(file_id)
+ out_file = self.out_dir / self.generated_files[file_id]
+ out_file.write_text(data, encoding='ascii')
+ mlog.log('Generated', mlog.bold(out_file.name))
+
+ def _write_template(self, data: T.Dict[str, T.Any], file_id: str, template_name: T.Optional[str] = None) -> None:
+ ''' Render the template mustache files and write the result '''
+ template_dir = Path(__file__).resolve().parent / 'templates'
+ template_name = template_name or file_id
+ template_name = f'{template_name}.mustache'
+ template_file = template_dir / template_name
+
+ # Import here, so that other generators don't also depend on it
+ import chevron
+ result = chevron.render(
+ template=template_file.read_text(encoding='utf-8'),
+ data=data,
+ partials_path=template_dir.as_posix(),
+ warn=True,
+ )
+
+ self._write_file(result, file_id)
+
+
+ # Actual generator functions
+ def _gen_func_or_method(self, func: Function) -> FunctionDictType:
+ def render_type(typ: Type, in_code_block: bool = False) -> str:
+ def data_type_to_str(dt: DataTypeInfo) -> str:
+ base = self._link_to_object(dt.data_type, in_code_block)
+ if dt.holds:
+ return f'{base}[{render_type(dt.holds, in_code_block)}]'
+ return base
+ assert typ.resolved
+ return ' | '.join([data_type_to_str(x) for x in typ.resolved])
+
+ def len_stripped(s: str) -> int:
+ s = s.replace(']]', '')
+ # I know, this regex is ugly but it works.
+ return len(re.sub(r'\[\[(#|@)*([^\[])', r'\2', s))
+
+ def render_signature() -> str:
+ # Skip a lot of computations if the function does not take any arguments
+ if not any([func.posargs, func.optargs, func.kwargs, func.varargs]):
+ return f'{render_type(func.returns, True)} {func.name}()'
+
+ signature = dedent(f'''\
+ # {self.brief(func)}
+ {render_type(func.returns, True)} {func.name}(
+ ''')
+
+ # Calculate maximum lengths of the type and name
+ all_args: T.List[ArgBase] = []
+ all_args += func.posargs
+ all_args += func.optargs
+ all_args += [func.varargs] if func.varargs else []
+
+ max_type_len = 0
+ max_name_len = 0
+ if all_args:
+ max_type_len = max([len_stripped(render_type(x.type)) for x in all_args])
+ max_name_len = max([len(x.name) for x in all_args])
+
+ # Generate some common strings
+ def prepare(arg: ArgBase) -> T.Tuple[str, str, str, str]:
+ type_str = render_type(arg.type, True)
+ type_len = len_stripped(type_str)
+ type_space = ' ' * (max_type_len - type_len)
+ name_space = ' ' * (max_name_len - len(arg.name))
+ name_str = f'<b>{arg.name.replace("<", "&lt;").replace(">", "&gt;")}</b>'
+ return type_str, type_space, name_str, name_space
+
+ for i in func.posargs:
+ type_str, type_space, name_str, name_space = prepare(i)
+ signature += f' {type_str}{type_space} {name_str},{name_space} # {self.brief(i)}\n'
+
+ for i in func.optargs:
+ type_str, type_space, name_str, name_space = prepare(i)
+ signature += f' {type_str}{type_space} [{name_str}],{name_space} # {self.brief(i)}\n'
+
+ if func.varargs:
+ type_str, type_space, name_str, name_space = prepare(func.varargs)
+ signature += f' {type_str}{type_space} {name_str}...,{name_space} # {self.brief(func.varargs)}\n'
+
+ # Abort if there are no kwargs
+ if not func.kwargs:
+ return signature + ')'
+
+ # Only add this seperator if there are any posargs
+ if all_args:
+ signature += '\n # Keyword arguments:\n'
+
+ # Recalculate lengths for kwargs
+ all_args = list(func.kwargs.values())
+ max_type_len = max([len_stripped(render_type(x.type)) for x in all_args])
+ max_name_len = max([len(x.name) for x in all_args])
+
+ for kwarg in self.sorted_and_filtered(list(func.kwargs.values())):
+ type_str, type_space, name_str, name_space = prepare(kwarg)
+ required = ' <i>[required]</i> ' if kwarg.required else ' '
+ required = required if any([x.required for x in func.kwargs.values()]) else ''
+ signature += f' {name_str}{name_space} : {type_str}{type_space} {required} # {self.brief(kwarg)}\n'
+
+ return signature + ')'
+
+ def gen_arg_data(arg: T.Union[PosArg, Kwarg, VarArgs], *, optional: bool = False) -> T.Dict[str, PlaceholderTypes]:
+ data: T.Dict[str, PlaceholderTypes] = {
+ 'name': arg.name,
+ 'type': render_type(arg.type),
+ 'description': arg.description,
+ 'since': arg.since or None,
+ 'deprecated': arg.deprecated or None,
+ 'optional': optional,
+ 'default': None,
+ }
+
+ if isinstance(arg, VarArgs):
+ data.update({
+ 'min': str(arg.min_varargs) if arg.min_varargs > 0 else '0',
+ 'max': str(arg.max_varargs) if arg.max_varargs > 0 else 'infinity',
+ })
+ if isinstance(arg, (Kwarg, PosArg)):
+ data.update({'default': arg.default or None})
+ if isinstance(arg, Kwarg):
+ data.update({'required': arg.required})
+ return data
+
+ mname = f'\\{func.name}' if func.name == '[index]' else func.name
+
+ data: FunctionDictType = {
+ 'name': f'{func.obj.name}.{mname}' if isinstance(func, Method) else func.name,
+ 'base_level': '##' if isinstance(func, Method) else '#',
+ 'type_name_upper': 'Method' if isinstance(func, Method) else 'Function',
+ 'type_name': 'method' if isinstance(func, Method) else 'function',
+ 'description': func.description,
+ 'notes': func.notes,
+ 'warnings': func.warnings,
+ 'example': func.example or None,
+ 'signature_level': 'h4' if isinstance(func, Method) else 'h3',
+ 'signature': render_signature(),
+ 'has_args': bool(func.posargs or func.optargs or func.kwargs or func.varargs),
+ # Merge posargs and optargs by generating the *[optional]* tag for optargs
+ 'posargs': {
+ 'args': [gen_arg_data(x) for x in func.posargs] + [gen_arg_data(x, optional=True) for x in func.optargs]
+ } if func.posargs or func.optargs else None,
+ 'kwargs': {'args': [gen_arg_data(x) for x in self.sorted_and_filtered(list(func.kwargs.values()))]} if func.kwargs else None,
+ 'varargs': gen_arg_data(func.varargs) if func.varargs else None,
+
+ # For the feature taggs template
+ 'since': func.since or None,
+ 'deprecated': func.deprecated or None,
+ 'optional': False,
+ 'default': None
+ }
+
+ return data
+
+ def _write_object(self, obj: Object) -> None:
+ data = {
+ 'name': obj.name,
+ 'description': obj.description,
+ 'notes': obj.notes,
+ 'warnings': obj.warnings,
+ 'long_name': obj.long_name,
+ 'obj_type_name': _OBJ_ID_MAP[obj.obj_type].capitalize(),
+ 'example': obj.example or None,
+ 'has_methods': bool(obj.methods),
+ 'has_inherited_methods': bool(obj.inherited_methods),
+ 'has_subclasses': bool(obj.extended_by),
+ 'is_returned': bool(obj.returned_by),
+ 'extends': obj.extends_obj.name if obj.extends_obj else None,
+ 'returned_by': [self._link_to_object(x) for x in self.sorted_and_filtered(obj.returned_by)],
+ 'extended_by': [self._link_to_object(x) for x in self.sorted_and_filtered(obj.extended_by)],
+ 'methods': [self._gen_func_or_method(m) for m in self.sorted_and_filtered(obj.methods)],
+ 'inherited_methods': [self._gen_func_or_method(m) for m in self.sorted_and_filtered(obj.inherited_methods)],
+ }
+
+ self._write_template(data, self._gen_object_file_id(obj), 'object')
+
+ def _write_functions(self) -> None:
+ data = {'functions': [self._gen_func_or_method(x) for x in self.functions]}
+ self._write_template(data, 'root.functions')
+
+ def _root_refman_docs(self) -> None:
+ def gen_obj_links(objs: T.List[Object]) -> T.List[T.Dict[str, str]]:
+ ret: T.List[T.Dict[str, str]] = []
+ for o in objs:
+ ret += [{'indent': '', 'link': self._link_to_object(o), 'brief': self.brief(o)}]
+ for m in self.sorted_and_filtered(o.methods):
+ ret += [{'indent': ' ', 'link': self._link_to_object(m), 'brief': self.brief(m)}]
+ if o.obj_type == ObjectType.MODULE and self.extract_returned_by_module(o):
+ tmp = gen_obj_links(self.extract_returned_by_module(o))
+ tmp = [{**x, 'indent': ' ' + x['indent']} for x in tmp]
+ ret += [{'indent': ' ', 'link': '**New objects:**', 'brief': ''}]
+ ret += [*tmp]
+ return ret
+
+ data = {
+ 'root': self._gen_filename('root'),
+ 'elementary': gen_obj_links(self.elementary),
+ 'returned': gen_obj_links(self.returned),
+ 'builtins': gen_obj_links(self.builtins),
+ 'modules': gen_obj_links(self.modules),
+ 'functions': [{'indent': '', 'link': self._link_to_object(x), 'brief': self.brief(x)} for x in self.functions],
+ }
+
+ dummy = {'root': self._gen_filename('root')}
+
+ self._write_template(data, 'root')
+ self._write_template({**dummy, 'name': 'Elementary types'}, f'root.{_OBJ_ID_MAP[ObjectType.ELEMENTARY]}', 'dummy')
+ self._write_template({**dummy, 'name': 'Builtin objects'}, f'root.{_OBJ_ID_MAP[ObjectType.BUILTIN]}', 'dummy')
+ self._write_template({**dummy, 'name': 'Returned objects'}, f'root.{_OBJ_ID_MAP[ObjectType.RETURNED]}', 'dummy')
+ self._write_template({**dummy, 'name': 'Modules'}, f'root.{_OBJ_ID_MAP[ObjectType.MODULE]}', 'dummy')
+
+
+ def generate(self) -> None:
+ mlog.log('Generating markdown files...')
+ with mlog.nested():
+ self._write_functions()
+ for obj in self.objects:
+ self._write_object(obj)
+ self._root_refman_docs()
+ self._configure_sitemap()
+ self._generate_link_def()
+
+ def _configure_sitemap(self) -> None:
+ '''
+ Replaces the `@REFMAN_PLACEHOLDER@` placeholder with the reference
+ manual sitemap. The structure of the sitemap is derived from the
+ file IDs.
+ '''
+ raw = self.sitemap_in.read_text(encoding='utf-8')
+ out = ''
+ for l in raw.split('\n'):
+ if '@REFMAN_PLACEHOLDER@' not in l:
+ out += f'{l}\n'
+ continue
+ mlog.log('Generating', mlog.bold(self.sitemap_out.as_posix()))
+ base_indent = l.replace('@REFMAN_PLACEHOLDER@', '')
+ for k in sorted(self.generated_files.keys()):
+ indent = base_indent + '\t' * k.count('.')
+ out += f'{indent}{self.generated_files[k]}\n'
+ self.sitemap_out.write_text(out, encoding='utf-8')
+
+ def _generate_link_def(self) -> None:
+ '''
+ Generate the link definition file for the refman_links hotdoc
+ plugin. The plugin is then responsible for replacing the [[tag]]
+ tags with custom HTML elements.
+ '''
+ data: T.Dict[str, str] = {}
+
+ # Objects and methods
+ for obj in self.objects:
+ obj_file = self._gen_filename(self._gen_object_file_id(obj), extension='html')
+ data[f'@{obj.name}'] = obj_file
+ for m in obj.methods:
+ data[f'{obj.name}.{m.name}'] = f'{obj_file}#{obj.name}{m.name}'
+
+ # Functions
+ funcs_file = self._gen_filename('root.functions', extension='html')
+ for fn in self.functions:
+ data[fn.name] = f'{funcs_file}#{fn.name}'
+
+ self.link_def_out.write_text(json.dumps(data, indent=2), encoding='utf-8')
diff --git a/docs/refman/generatorpickle.py b/docs/refman/generatorpickle.py
new file mode 100644
index 0000000..364a988
--- /dev/null
+++ b/docs/refman/generatorpickle.py
@@ -0,0 +1,26 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pickle
+from pathlib import Path
+from .generatorbase import GeneratorBase
+from .model import ReferenceManual
+
+class GeneratorPickle(GeneratorBase):
+ def __init__(self, manual: ReferenceManual, outpath: Path) -> None:
+ self.out = outpath
+ super().__init__(manual)
+
+ def generate(self) -> None:
+ self.out.write_bytes(pickle.dumps(self.manual))
diff --git a/docs/refman/generatorprint.py b/docs/refman/generatorprint.py
new file mode 100644
index 0000000..d836091
--- /dev/null
+++ b/docs/refman/generatorprint.py
@@ -0,0 +1,87 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .generatorbase import GeneratorBase
+from .model import ReferenceManual, Object, Function, DataTypeInfo, Type, ObjectType
+
+from mesonbuild import mlog
+import typing as T
+
+def my_nested() -> T.ContextManager[None]:
+ prefix = '|' * len(mlog.log_depth)
+ return mlog.nested(prefix)
+
+class GeneratorPrint(GeneratorBase):
+ def _types_to_string(self, typ: Type) -> str:
+ def _data_type_to_str(dt: DataTypeInfo) -> str:
+ if dt.holds:
+ return f'{dt.data_type.name}[{self._types_to_string(dt.holds)}]'
+ return dt.data_type.name
+ return ' | '.join([_data_type_to_str(x) for x in typ.resolved])
+
+ def _generate_function(self, func: Function) -> None:
+ mlog.log()
+ mlog.log('Function', mlog.bold(func.name))
+ with my_nested():
+ desc = func.description
+ if '\n' in desc:
+ desc = desc[:desc.index('\n')]
+ mlog.log('Description:', mlog.bold(desc))
+ mlog.log('Return type:', mlog.bold(self._types_to_string(func.returns)))
+ mlog.log('Pos args: ', mlog.bold(str([x.name for x in func.posargs])))
+ mlog.log('Opt args: ', mlog.bold(str([x.name for x in func.optargs])))
+ mlog.log('Varargs: ', mlog.bold(func.varargs.name if func.varargs is not None else 'null'))
+ mlog.log('Kwargs: ', mlog.bold(str(list(func.kwargs.keys()))))
+
+ def _generate_object(self, obj: Object) -> None:
+ tags = []
+ tags += [{
+ ObjectType.ELEMENTARY: mlog.yellow('[elementary]'),
+ ObjectType.BUILTIN: mlog.green('[builtin]'),
+ ObjectType.MODULE: mlog.blue('[module]'),
+ ObjectType.RETURNED: mlog.cyan('[returned]'),
+ }[obj.obj_type]]
+ if obj.is_container:
+ tags += [mlog.red('[container]')]
+ mlog.log()
+ mlog.log('Object', mlog.bold(obj.name), *tags)
+ with my_nested():
+ desc = obj.description
+ if '\n' in desc:
+ desc = desc[:desc.index('\n')]
+ mlog.log('Description:', mlog.bold(desc))
+ mlog.log('Returned by:', mlog.bold(str([x.name for x in obj.returned_by])))
+ mlog.log('Methods:')
+ with my_nested():
+ for m in obj.methods:
+ self._generate_function(m)
+
+ def generate(self) -> None:
+ mlog.log('\n\n', mlog.bold('=== Functions ==='), '\n')
+ for f in self.functions:
+ self._generate_function(f)
+ mlog.log('\n\n', mlog.bold('=== Elementary ==='), '\n')
+ for obj in self.elementary:
+ self._generate_object(obj)
+ mlog.log('\n\n', mlog.bold('=== Builtins ==='), '\n')
+ for obj in self.builtins:
+ self._generate_object(obj)
+ mlog.log('\n\n', mlog.bold('=== Returned objects ==='), '\n')
+ for obj in self.returned:
+ self._generate_object(obj)
+ mlog.log('\n\n', mlog.bold('=== Modules ==='), '\n')
+ for obj in self.modules:
+ self._generate_object(obj)
+ for mod_obj in self.extract_returned_by_module(obj):
+ self._generate_object(mod_obj)
diff --git a/docs/refman/loaderbase.py b/docs/refman/loaderbase.py
new file mode 100644
index 0000000..1db92e2
--- /dev/null
+++ b/docs/refman/loaderbase.py
@@ -0,0 +1,216 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from abc import ABCMeta, abstractmethod
+from pathlib import Path
+import re
+import typing as T
+
+from .model import (
+ NamedObject,
+ FetureCheck,
+ ArgBase,
+ PosArg,
+ DataTypeInfo,
+ Type,
+ Function,
+ Method,
+ Object,
+ ObjectType,
+ ReferenceManual,
+)
+
+from mesonbuild import mlog
+
+class _Resolver:
+ def __init__(self) -> None:
+ self.type_map: T.Dict[str, Object] = {}
+ self.func_map: T.Dict[str, T.Union[Function, Method]] = {}
+ self.processed_funcs: T.Set[str] = set()
+
+ def _validate_named_object(self, obj: NamedObject) -> None:
+ name_regex = re.compile(r'[a-zA-Z0-9_]+')
+ obj.name = obj.name.strip()
+ obj.description = obj.description.strip()
+ assert obj.name and obj.description, 'Both name and description must be set'
+ assert obj.name.islower(), f'Object names must be lower case ({obj.name})'
+ assert name_regex.match(obj.name) or obj.name == '[index]', f'Invalid name {obj.name}'
+
+ def _validate_feature_check(self, obj: FetureCheck) -> None:
+ meson_version_reg = re.compile(r'[0-9]+\.[0-9]+\.[0-9]+')
+ obj.since = obj.since.strip()
+ obj.deprecated = obj.deprecated.strip()
+ if obj.since:
+ assert meson_version_reg.match(obj.since)
+ if obj.deprecated:
+ assert meson_version_reg.match(obj.deprecated)
+
+ def _resolve_type(self, raw: str) -> Type:
+ typ = Type(raw)
+ # We can't use `types = raw.split('|')`, because of `list[str | env]`
+ types: T.List[str] = ['']
+ stack = 0
+ for c in raw:
+ if stack == 0 and c == '|':
+ types += ['']
+ continue
+ if c == '[':
+ stack += 1
+ if c == ']':
+ stack -= 1
+ types[-1] += c
+ types = [x.strip() for x in types]
+ for t in types:
+ t = t.strip()
+ idx = t.find('[')
+ base_type = t
+ held_type = None
+ if idx > 0:
+ base_type = t[:idx]
+ held_type = self._resolve_type(t[idx+1:-1])
+ assert base_type in self.type_map, f'No known object {t}'
+ obj = self.type_map[base_type]
+ typ.resolved += [DataTypeInfo(obj, held_type)]
+ return typ
+
+ def _validate_func(self, func: T.Union[Function, Method]) -> None:
+ # Always run basic checks, since they also slightly post-process (strip) some strings
+ self._validate_named_object(func)
+ self._validate_feature_check(func)
+
+ func_id = f'{func.obj.name}.{func.name}' if isinstance(func, Method) else func.name
+ if func_id in self.processed_funcs:
+ return
+
+ func.returns = self._resolve_type(func.returns.raw)
+
+ all_args: T.List[ArgBase] = []
+ all_args += func.posargs
+ all_args += func.optargs
+ all_args += func.kwargs.values()
+ all_args += [func.varargs] if func.varargs else []
+
+ for arg in all_args:
+ arg.type = self._resolve_type(arg.type.raw)
+
+ # Handle returned_by
+ for obj in func.returns.resolved:
+ obj.data_type.returned_by += [func]
+
+ # Handle kwargs inehritance
+ for base_name in func.kwargs_inherit:
+ base_name = base_name.strip()
+ assert base_name in self.func_map, f'Unknown base function `{base_name}` for {func.name}'
+ base = self.func_map[base_name]
+ if base_name not in self.processed_funcs:
+ self._validate_func(base)
+
+ curr_keys = set(func.kwargs.keys())
+ base_keys = set(base.kwargs.keys())
+
+ # Calculate the missing kwargs from the current set
+ missing = {k: v for k, v in base.kwargs.items() if k in base_keys - curr_keys}
+ func.kwargs.update(missing)
+
+ # Handloe other args inheritance
+ _T = T.TypeVar('_T', bound=T.Union[ArgBase, T.List[PosArg]])
+ def resolve_inherit(name: str, curr: _T, resolver: T.Callable[[Function], _T]) -> _T:
+ if name and not curr:
+ name = name.strip()
+ assert name in self.func_map, f'Unknown base function `{name}` for {func.name}'
+ if name not in self.processed_funcs:
+ self._validate_func(self.func_map[name])
+ ref_args = resolver(self.func_map[name])
+ assert ref_args is not None, f'Inherited function `{name}` does not have inherited args set'
+ return ref_args
+ return curr
+
+ func.posargs = resolve_inherit(func.posargs_inherit, func.posargs, lambda x: x.posargs)
+ func.optargs = resolve_inherit(func.optargs_inherit, func.optargs, lambda x: x.optargs)
+ func.varargs = resolve_inherit(func.varargs_inherit, func.varargs, lambda x: x.varargs)
+
+ self.processed_funcs.add(func_id)
+
+ def validate_and_resolve(self, manual: ReferenceManual) -> ReferenceManual:
+ mlog.log('Validating loaded manual...')
+
+ # build type map and func map for methods
+ for obj in manual.objects:
+ assert obj.name not in self.type_map, f'Duplicate object name {obj.name}'
+ self.type_map[obj.name] = obj
+ for m in obj.methods:
+ mid = f'{obj.name}.{m.name}'
+ assert mid not in self.type_map, f'Duplicate metod {mid}'
+ self.func_map[mid] = m
+
+ # Build func map for functions
+ for func in manual.functions:
+ assert func.name not in [*self.func_map.keys()], f'Duplicate function {func.name}'
+ self.func_map[func.name] = func
+
+ mlog.log('Validating functions...')
+ for func in manual.functions:
+ mlog.log(' -- validating', mlog.bold(func.name))
+ self._validate_func(func)
+
+ mlog.log('Validating objects...')
+ for obj in manual.objects:
+ mlog.log(' -- validating', mlog.bold(obj.name))
+ self._validate_named_object(obj)
+ self._validate_feature_check(obj)
+ # Resolve and validate inheritence
+ if obj.extends:
+ assert obj.extends in self.type_map, f'Unknown extends object {obj.extends} in {obj.name}'
+ obj.extends_obj = self.type_map[obj.extends]
+ obj.extends_obj.extended_by += [obj]
+ # Only returned objects can be associated with module
+ if obj.obj_type is not ObjectType.RETURNED:
+ assert obj.defined_by_module is None
+ for m in obj.methods:
+ assert m.obj is obj
+ self._validate_func(m)
+
+ # Resolve inherited methods
+ for obj in manual.objects:
+ inherited_methods = obj.inherited_methods
+ curr = obj.extends_obj
+ while curr is not None:
+ inherited_methods += curr.methods
+ curr = curr.extends_obj
+ return manual
+
+class LoaderBase(metaclass=ABCMeta):
+ def __init__(self) -> None:
+ self._input_files: T.List[Path] = []
+
+ @property
+ def input_files(self) -> T.List[Path]:
+ return list(self._input_files)
+
+ def read_file(self, f: Path) -> str:
+ assert f.exists()
+ assert f.is_file()
+ self._input_files += [f.resolve()]
+ return f.read_text(encoding='utf-8')
+
+ @abstractmethod
+ def load_impl(self) -> ReferenceManual:
+ pass
+
+ def load(self) -> ReferenceManual:
+ self._input_files = [] # Reset input files
+ manual = self.load_impl()
+ resolver = _Resolver()
+ with mlog.nested():
+ return resolver.validate_and_resolve(manual)
diff --git a/docs/refman/loaderyaml.py b/docs/refman/loaderyaml.py
new file mode 100644
index 0000000..1f80185
--- /dev/null
+++ b/docs/refman/loaderyaml.py
@@ -0,0 +1,203 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .loaderbase import LoaderBase
+from .model import (
+ Type,
+ PosArg,
+ VarArgs,
+ Kwarg,
+ Function,
+ Method,
+ ObjectType,
+ Object,
+ ReferenceManual,
+)
+
+from mesonbuild import mlog
+from mesonbuild import mesonlib
+
+from strictyaml import Map, MapPattern, Optional, Str, Seq, Int, Bool, load, EmptyList, OrValidator
+from pathlib import Path
+import typing as T
+
+d_named_object = {
+ 'name': Str(),
+ 'description': Str(),
+}
+
+d_feture_check = {
+ Optional('since', default=''): Str(),
+ Optional('deprecated', default=''): Str(),
+}
+
+s_posarg = Map({
+ **d_feture_check,
+ 'description': Str(),
+ 'type': Str(),
+ Optional('default', default=''): Str(),
+})
+
+s_varargs = Map({
+ **d_named_object, **d_feture_check,
+ 'type': Str(),
+ Optional('min_varargs', default=-1): Int(),
+ Optional('max_varargs', default=-1): Int(),
+})
+
+s_kwarg = Map({
+ **d_feture_check,
+ 'type': Str(),
+ 'description': Str(),
+ Optional('required', default=False): Bool(),
+ Optional('default', default=''): Str(),
+})
+
+s_function = Map({
+ **d_named_object, **d_feture_check,
+ 'returns': Str(),
+ Optional('notes', default=[]): OrValidator(Seq(Str()), EmptyList()),
+ Optional('warnings', default=[]): OrValidator(Seq(Str()), EmptyList()),
+ Optional('example', default=''): Str(),
+ Optional('posargs'): MapPattern(Str(), s_posarg),
+ Optional('optargs'): MapPattern(Str(), s_posarg),
+ Optional('varargs'): s_varargs,
+ Optional('posargs_inherit', default=''): Str(),
+ Optional('optargs_inherit', default=''): Str(),
+ Optional('varargs_inherit', default=''): Str(),
+ Optional('kwargs'): MapPattern(Str(), s_kwarg),
+ Optional('kwargs_inherit', default=[]): OrValidator(OrValidator(Seq(Str()), EmptyList()), Str()),
+})
+
+s_object = Map({
+ **d_named_object, **d_feture_check,
+ 'long_name': Str(),
+ Optional('extends', default=''): Str(),
+ Optional('notes', default=[]): OrValidator(Seq(Str()), EmptyList()),
+ Optional('warnings', default=[]): OrValidator(Seq(Str()), EmptyList()),
+ Optional('example', default=''): Str(),
+ Optional('methods'): Seq(s_function),
+ Optional('is_container', default=False): Bool()
+})
+
+class LoaderYAML(LoaderBase):
+ def __init__(self, yaml_dir: Path) -> None:
+ super().__init__()
+ self.yaml_dir = yaml_dir
+ self.func_dir = self.yaml_dir / 'functions'
+ self.elem_dir = self.yaml_dir / 'elementary'
+ self.objs_dir = self.yaml_dir / 'objects'
+ self.builtin_dir = self.yaml_dir / 'builtins'
+ self.modules_dir = self.yaml_dir / 'modules'
+
+ def _process_function_base(self, raw: T.OrderedDict, obj: T.Optional[Object] = None) -> Function:
+ # Handle arguments
+ posargs = raw.pop('posargs', {})
+ optargs = raw.pop('optargs', {})
+ varargs = raw.pop('varargs', None)
+ kwargs = raw.pop('kwargs', {})
+
+ # Fix kwargs_inherit
+ if isinstance(raw['kwargs_inherit'], str):
+ raw['kwargs_inherit'] = [raw['kwargs_inherit']]
+
+ # Parse args
+ posargs_mapped: T.List[PosArg] = []
+ optargs_mapped: T.List[PosArg] = []
+ varargs_mapped: T.Optional[VarArgs] = None
+ kwargs_mapped: T.Dict[str, Kwarg] = {}
+
+ for k, v in posargs.items():
+ v['type'] = Type(v['type'])
+ posargs_mapped += [PosArg(name=k, **v)]
+
+ for k, v in optargs.items():
+ v['type'] = Type(v['type'])
+ optargs_mapped += [PosArg(name=k, **v)]
+
+ for k, v in kwargs.items():
+ v['type'] = Type(v['type'])
+ kwargs_mapped[k] = Kwarg(name=k, **v)
+
+ if varargs is not None:
+ varargs['type'] = Type(varargs['type'])
+ varargs_mapped = VarArgs(**varargs)
+
+ raw['returns'] = Type(raw['returns'])
+
+ # Build function object
+ if obj is not None:
+ return Method(
+ posargs=posargs_mapped,
+ optargs=optargs_mapped,
+ varargs=varargs_mapped,
+ kwargs=kwargs_mapped,
+ obj=obj,
+ **raw,
+ )
+ return Function(
+ posargs=posargs_mapped,
+ optargs=optargs_mapped,
+ varargs=varargs_mapped,
+ kwargs=kwargs_mapped,
+ **raw,
+ )
+
+ def _load_function(self, path: Path, obj: T.Optional[Object] = None) -> Function:
+ path_label = path.relative_to(self.yaml_dir).as_posix()
+ mlog.log('Loading', mlog.bold(path_label))
+ raw = load(self.read_file(path), s_function, label=path_label).data
+ return self._process_function_base(raw)
+
+ def _load_object(self, obj_type: ObjectType, path: Path) -> Object:
+ path_label = path.relative_to(self.yaml_dir).as_posix()
+ mlog.log(f'Loading', mlog.bold(path_label))
+ raw = load(self.read_file(path), s_object, label=path_label).data
+
+ def as_methods(mlist: T.List[Function]) -> T.List[Method]:
+ res: T.List[Method] = []
+ for i in mlist:
+ assert isinstance(i, Method)
+ res += [i]
+ return res
+
+ methods = raw.pop('methods', [])
+ obj = Object(methods=[], obj_type=obj_type, **raw)
+ obj.methods = as_methods([self._process_function_base(x, obj) for x in methods])
+ return obj
+
+ def _load_module(self, path: Path) -> T.List[Object]:
+ assert path.is_dir()
+ module = self._load_object(ObjectType.MODULE, path / 'module.yaml')
+ objs = []
+ for p in path.iterdir():
+ if p.name == 'module.yaml':
+ continue
+ obj = self._load_object(ObjectType.RETURNED, p)
+ obj.defined_by_module = module
+ objs += [obj]
+ return [module, *objs]
+
+ def load_impl(self) -> ReferenceManual:
+ mlog.log('Loading YAML refererence manual')
+ with mlog.nested():
+ return ReferenceManual(
+ functions=[self._load_function(x) for x in self.func_dir.iterdir()],
+ objects=mesonlib.listify([
+ [self._load_object(ObjectType.ELEMENTARY, x) for x in self.elem_dir.iterdir()],
+ [self._load_object(ObjectType.RETURNED, x) for x in self.objs_dir.iterdir()],
+ [self._load_object(ObjectType.BUILTIN, x) for x in self.builtin_dir.iterdir()],
+ [self._load_module(x) for x in self.modules_dir.iterdir()]
+ ], flatten=True)
+ )
diff --git a/docs/refman/main.py b/docs/refman/main.py
new file mode 100644
index 0000000..cb040ce
--- /dev/null
+++ b/docs/refman/main.py
@@ -0,0 +1,75 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from pathlib import Path
+import argparse
+import typing as T
+
+from mesonbuild import mlog
+
+from .loaderbase import LoaderBase
+from .loaderyaml import LoaderYAML
+
+from .generatorbase import GeneratorBase
+from .generatorprint import GeneratorPrint
+from .generatorpickle import GeneratorPickle
+from .generatormd import GeneratorMD
+
+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'], help='Information loader backend')
+ parser.add_argument('-g', '--generator', type=str, choices=['print', 'pickle', 'md'], 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('--link-defs', type=Path, help='Output file for the MD generator link definition file')
+ parser.add_argument('--depfile', type=Path, default=None, help='Set to generate a depfile')
+ parser.add_argument('--force-color', action='store_true', help='Force enable colors')
+ args = parser.parse_args()
+
+ if args.force_color:
+ mlog.colorize_console = lambda: True
+
+ loaders: T.Dict[str, T.Callable[[], LoaderBase]] = {
+ 'yaml': lambda: LoaderYAML(meson_root / 'docs' / 'yaml'),
+ }
+
+ loader = loaders[args.loader]()
+ refMan = loader.load()
+
+ generators: T.Dict[str, T.Callable[[], GeneratorBase]] = {
+ 'print': lambda: GeneratorPrint(refMan),
+ 'pickle': lambda: GeneratorPickle(refMan, args.out),
+ 'md': lambda: GeneratorMD(refMan, args.out, args.sitemap, args.link_defs),
+ }
+ generator = generators[args.generator]()
+
+ # Generate the depfile if required
+ if args.depfile is not None:
+ assert isinstance(args.depfile, Path)
+ assert isinstance(args.out, Path)
+
+ # Also add all files of this package
+ script_files = list(Path(__file__).resolve().parent.glob('**/*.py'))
+ templates = list(Path(__file__).resolve().parent.glob('**/*.mustache'))
+
+ out_text = f'{args.out.resolve().as_posix()}: \\\n'
+ for input in loader.input_files + script_files + templates:
+ out_text += f' {input.resolve().as_posix():<93} \\\n'
+
+ args.depfile.write_text(out_text, encoding='utf-8')
+
+ generator.generate()
+ return 0
diff --git a/docs/refman/model.py b/docs/refman/model.py
new file mode 100644
index 0000000..23515a2
--- /dev/null
+++ b/docs/refman/model.py
@@ -0,0 +1,113 @@
+# Copyright 2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from dataclasses import dataclass, field
+from enum import Enum
+import typing as T
+
+# Utils
+@dataclass
+class NamedObject:
+ name: str
+ description: str
+
+ @property
+ def hidden(self) -> bool:
+ return self.name.startswith('_')
+
+@dataclass
+class FetureCheck:
+ since: str
+ deprecated: str
+
+@dataclass
+class DataTypeInfo:
+ data_type: 'Object'
+ holds: T.Optional['Type']
+
+@dataclass
+class Type:
+ raw: str
+ resolved: T.List[DataTypeInfo] = field(init=False, default_factory=list)
+
+
+# Arguments
+@dataclass
+class ArgBase(NamedObject):
+ type: Type
+
+@dataclass
+class PosArg(ArgBase, FetureCheck):
+ default: str
+
+@dataclass
+class VarArgs(ArgBase, FetureCheck):
+ min_varargs: int
+ max_varargs: int
+
+@dataclass
+class Kwarg(ArgBase, FetureCheck):
+ required: bool
+ default: str
+
+
+# Function
+@dataclass
+class Function(NamedObject, FetureCheck):
+ notes: T.List[str]
+ warnings: T.List[str]
+ returns: Type
+ example: str
+ posargs: T.List[PosArg]
+ optargs: T.List[PosArg]
+ varargs: T.Optional[VarArgs]
+ kwargs: T.Dict[str, Kwarg]
+ posargs_inherit: str
+ optargs_inherit: str
+ varargs_inherit: str
+ kwargs_inherit: T.List[str]
+
+@dataclass
+class Method(Function):
+ obj: 'Object'
+
+
+# Types and objects
+class ObjectType(Enum):
+ ELEMENTARY = 0
+ BUILTIN = 1
+ MODULE = 2
+ RETURNED = 3
+
+@dataclass
+class Object(NamedObject, FetureCheck):
+ notes: T.List[str]
+ warnings: T.List[str]
+ long_name: str
+ example: str
+ obj_type: ObjectType
+ methods: T.List[Method]
+ is_container: bool
+ extends: str
+ extends_obj: T.Optional['Object'] = None
+ defined_by_module: T.Optional['Object'] = None
+ returned_by: T.List[T.Union[Function, Method]] = field(default_factory=list)
+ extended_by: T.List['Object'] = field(default_factory=list)
+ inherited_methods: T.List[Method] = field(default_factory=list)
+
+# ROOT
+@dataclass
+class ReferenceManual:
+ functions: T.List[Function]
+ objects: T.List[Object]
diff --git a/docs/refman/templates/args.mustache b/docs/refman/templates/args.mustache
new file mode 100644
index 0000000..802bcd6
--- /dev/null
+++ b/docs/refman/templates/args.mustache
@@ -0,0 +1,28 @@
+<!-- Hotdoc / markdown inserts <p> around the table elements. Override the margin to make them invisible -->
+<style>
+ .nomargin p:last-child { margin-bottom: 0px !important; }
+</style>
+<table>
+ <thead>
+ <tr>
+ <th style="white-space: nowrap; text-align: center;">Name</th>
+ <th style="white-space: nowrap; text-align: center;">Type</th>
+ <th style="width: 56%;">Description</th>
+ <th style="white-space: nowrap; text-align: center; width: 0px;">Tags</th>
+ </tr>
+ </thead>
+ <tbody class="nomargin">
+ {{#args}}
+ <tr>
+ <td style="white-space: nowrap; text-align: center; padding: 6px;"><code class="language-meson">{{name}}</code></td>
+ <td style="white-space: revert; text-align: center; padding: 6px; word-wrap: break-word;">{{&type}}</td>
+ <!-- This suboptimal formating is required to ensure hotdoc correctly generates the HTML -->
+ <td style="width: 56%; padding: 6px;">
+
+{{&description}}
+</td>
+ <td style="white-space: nowrap; text-align: center; padding: 6px; width: 0px;">{{>taggs}}</td>
+ </tr>
+ {{/args}}
+ </tbody>
+</table>
diff --git a/docs/refman/templates/dummy.mustache b/docs/refman/templates/dummy.mustache
new file mode 100644
index 0000000..ddb090e
--- /dev/null
+++ b/docs/refman/templates/dummy.mustache
@@ -0,0 +1,8 @@
+---
+short-description: {{name}}
+render-subpages: true
+...
+# {{name}}
+
+See the [root manual document]({{root}}) for
+a general overview.
diff --git a/docs/refman/templates/func.mustache b/docs/refman/templates/func.mustache
new file mode 100644
index 0000000..3e6209f
--- /dev/null
+++ b/docs/refman/templates/func.mustache
@@ -0,0 +1,55 @@
+{{base_level}}# {{name}}()
+
+{{&description}}
+
+<p style="padding: 5px; margin: 0px;"></p> <!-- A bit of space because we remove the top margin below -->
+<div style="display: flex;">
+ <{{signature_level}} style="margin-top: 0px;">Signature</{{signature_level}}>
+ <div style="flex-grow: 1;"></div>
+ <div>{{>taggs}}</div>
+</div>
+
+<pre><code class="language-meson">{{&signature}}</code></pre>
+
+{{#example}}
+ <p style="padding: 5px; margin: 0px;"></p> <!-- A bit more space -->
+
+ {{base_level}}## Example
+
+{{&example}}
+{{/example}}
+
+{{>notes}}
+
+{{#has_args}}
+ <p style="padding: 5px; margin: 0px;"></p> <!-- A bit more space -->
+
+ {{base_level}}## Arguments
+{{/has_args}}
+
+{{#posargs}}
+ The {{type_name}} `{{name}}()` accepts the following positional arguments:
+
+ {{>args}}
+
+ <p style="padding: 5px; margin: 0px;"></p> <!-- Extra space -->
+{{/posargs}}
+
+{{#varargs}}
+ {{#posargs}}Additionally, the{{/posargs}}{{^posargs}}The{{/posargs}}
+ {{type_name}} accepts between `{{min}}` and `{{max}}` variadic
+ arguments (`{{name}}...`) of type <code>{{&type}}</code>.
+
+ {{&description}}
+
+ {{>taggs}}
+
+ <p style="padding: 5px; margin: 0px;"></p> <!-- Extra space -->
+{{/varargs}}
+
+{{#kwargs}}
+ {{#posargs}}Finally, `{{name}}()`{{/posargs}}{{^posargs}}The {{type_name}} `{{name}}()`{{/posargs}}
+ accepts the following keyword arguments:
+
+ {{>args}}
+{{/kwargs}}
diff --git a/docs/refman/templates/notes.mustache b/docs/refman/templates/notes.mustache
new file mode 100644
index 0000000..de550c5
--- /dev/null
+++ b/docs/refman/templates/notes.mustache
@@ -0,0 +1,14 @@
+{{#notes}}
+<div class="alert alert-info">
+ <strong>Note:</strong>
+
+{{&.}}
+</div>
+{{/notes}}
+{{#warnings}}
+<div class="alert alert-warning">
+ <strong>Warning:</strong>
+
+{{&.}}
+</div>
+{{/warnings}}
diff --git a/docs/refman/templates/object.mustache b/docs/refman/templates/object.mustache
new file mode 100644
index 0000000..ec86034
--- /dev/null
+++ b/docs/refman/templates/object.mustache
@@ -0,0 +1,62 @@
+---
+short-description: "{{obj_type_name}} object: {{long_name}}"
+title: {{name}}{{#extends}} (extends {{.}}){{/extends}}
+render-subpages: false
+...
+# {{long_name}} (`{{name}}`{{#extends}} extends [[@{{.}}]]{{/extends}})
+
+{{&description}}
+
+{{#has_subclasses}}
+## Extended by
+
+{{long_name}} is extended by the following subtypes:
+{{#extended_by}}
+- {{&.}}
+{{/extended_by}}
+{{/has_subclasses}}
+
+{{#is_returned}}
+## Returned by
+
+{{long_name}} objects are returned by the following functions and methods:
+{{#returned_by}}
+- {{&.}}
+{{/returned_by}}
+{{/is_returned}}
+
+{{#example}}
+## Example
+
+<pre><code class="language-meson">{{&example}}</code></pre>
+{{/example}}
+
+{{>notes}}
+
+{{#has_methods}}
+## {{long_name}} methods
+
+{{#methods}}
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+{{>func}}
+
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+---
+{{/methods}}
+{{/has_methods}}
+
+{{#has_inherited_methods}}
+## Inherited methods
+
+{{#inherited_methods}}
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+{{>func}}
+
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+---
+{{/inherited_methods}}
+{{/has_inherited_methods}}
diff --git a/docs/refman/templates/root.functions.mustache b/docs/refman/templates/root.functions.mustache
new file mode 100644
index 0000000..33fba59
--- /dev/null
+++ b/docs/refman/templates/root.functions.mustache
@@ -0,0 +1,20 @@
+---
+short-description: Meson functions
+render-subpages: false
+...
+
+# Functions
+
+This document lists all functions available in `meson.build` files.
+See the [root manual document]({{root}}) for
+an overview of all features.
+
+{{#functions}}
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+{{>func}}
+
+<p style="padding: 7.5px; margin: 0px;"></p>
+
+---
+{{/functions}}
diff --git a/docs/refman/templates/root.mustache b/docs/refman/templates/root.mustache
new file mode 100644
index 0000000..cd846ac
--- /dev/null
+++ b/docs/refman/templates/root.mustache
@@ -0,0 +1,51 @@
+---
+short-description: The Meson reference manual
+render-subpages: false
+...
+
+# Reference manual
+
+This is the root page of the Meson reference manual. All functions
+and methods are documented in detail in the following subpages:
+
+## Elementary types
+
+{{#elementary}}
+{{indent}}- {{&link}}
+{{/elementary}}
+
+## Functions
+
+The following functions are available in build files. Click on each
+to see the description and usage. The objects returned by them are
+[listed here](#returned-objects).
+
+{{#functions}}
+{{indent}}- {{&link}}
+{{/functions}}
+
+## Builtin objects
+
+These are built-in objects that are always available.
+
+{{#builtins}}
+{{indent}}- {{&link}}
+{{/builtins}}
+
+## Returned objects
+
+These are objects that can be returned by [functions](#functions)
+or other methods.
+
+{{#returned}}
+{{indent}}- {{&link}}
+{{/returned}}
+
+## Modules
+
+{{#modules}}
+{{indent}}- {{&link}}
+{{/modules}}
+
+
+<!-- The links used to be generated wit {>root_link}, but this is a bit hard to read -->
diff --git a/docs/refman/templates/root_link.mustache b/docs/refman/templates/root_link.mustache
new file mode 100644
index 0000000..7d8b36b
--- /dev/null
+++ b/docs/refman/templates/root_link.mustache
@@ -0,0 +1 @@
+{{indent}}- <span style="display: flex;"><span>{{&link}}</span><span style="flex-grow: 1;"></span><span style="text-align: right;">{{&brief}}</span></span>
diff --git a/docs/refman/templates/taggs.mustache b/docs/refman/templates/taggs.mustache
new file mode 100644
index 0000000..c4286a0
--- /dev/null
+++ b/docs/refman/templates/taggs.mustache
@@ -0,0 +1,18 @@
+<div style="margin: 0px; text-align: center;">
+{{#since}}
+<p style="margin: 0px;"><em style="color: #5affff;">(since {{since}})</em></p>
+{{/since}}
+{{#deprecated}}
+<div style="color: #ffa844">
+ <p style="margin: 0px;"><strong>DEPRECATED</strong></p>
+ <p style="margin: 0px;"><em>in {{deprecated}}</em></p>
+</div>
+{{/deprecated}}
+{{#optional}}
+<p style="margin: 0px;"><b>[optional]</b></p>
+{{/optional}}
+{{#default}}
+<p style="margin: 0px;"><code class="language-meson">default =
+{{default}}</code></p>
+{{/default}}
+</div>