diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2021-10-04 22:19:07 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-04 22:19:07 +0300 |
commit | 2d65472c725f18b343aee00bf91b9ac98c08b95f (patch) | |
tree | 530a0d6ffee4ee96e875302cbeba660c93056d41 /docs/refman | |
parent | 75dd9fb67f793c687fa45744f3b276e35c87ca09 (diff) | |
parent | b672ebca886dd6dc9b0f775eb769764750fd302c (diff) | |
download | meson-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__.py | 0 | ||||
-rw-r--r-- | docs/refman/generatorbase.py | 71 | ||||
-rw-r--r-- | docs/refman/generatormd.py | 386 | ||||
-rw-r--r-- | docs/refman/generatorpickle.py | 26 | ||||
-rw-r--r-- | docs/refman/generatorprint.py | 87 | ||||
-rw-r--r-- | docs/refman/loaderbase.py | 216 | ||||
-rw-r--r-- | docs/refman/loaderyaml.py | 203 | ||||
-rw-r--r-- | docs/refman/main.py | 75 | ||||
-rw-r--r-- | docs/refman/model.py | 113 | ||||
-rw-r--r-- | docs/refman/templates/args.mustache | 28 | ||||
-rw-r--r-- | docs/refman/templates/dummy.mustache | 8 | ||||
-rw-r--r-- | docs/refman/templates/func.mustache | 55 | ||||
-rw-r--r-- | docs/refman/templates/notes.mustache | 14 | ||||
-rw-r--r-- | docs/refman/templates/object.mustache | 62 | ||||
-rw-r--r-- | docs/refman/templates/root.functions.mustache | 20 | ||||
-rw-r--r-- | docs/refman/templates/root.mustache | 51 | ||||
-rw-r--r-- | docs/refman/templates/root_link.mustache | 1 | ||||
-rw-r--r-- | docs/refman/templates/taggs.mustache | 18 |
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("<", "<").replace(">", ">")}</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> |