| from pathlib import Path | |
| from json import loads | |
| import os | |
| import re | |
| from hotdoc.core.exceptions import HotdocSourceException | |
| from hotdoc.core.extension import Extension | |
| from hotdoc.core.tree import Page | |
| from hotdoc.core.project import Project | |
| from hotdoc.core.symbols import * | |
| from hotdoc.run_hotdoc import Application | |
| from hotdoc.core.formatter import Formatter | |
| from hotdoc.utils.loggable import Logger, warn, info | |
| import typing as T | |
| if T.TYPE_CHECKING: | |
| import argparse | |
| Logger.register_warning_code('unknown-refman-link', HotdocSourceException, 'refman-links') | |
| class RefmanLinksExtension(Extension): | |
| extension_name = 'refman-links' | |
| argument_prefix = 'refman' | |
| def __init__(self, app: Application, project: Project): | |
| self.project: Project | |
| super().__init__(app, project) | |
| self._data_file: T.Optional[Path] = None | |
| self._data: T.Dict[str, str] = {} | |
| @staticmethod | |
| def add_arguments(parser: 'argparse.ArgumentParser'): | |
| group = parser.add_argument_group( | |
| 'Refman links', | |
| 'Custom Meson extension', | |
| ) | |
| # Add Arguments with `group.add_argument(...)` | |
| group.add_argument( | |
| f'--refman-data-file', | |
| help="JSON file with the mappings to replace", | |
| default=None, | |
| ) | |
| def parse_config(self, config: T.Dict[str, T.Any]) -> None: | |
| super().parse_config(config) | |
| self._data_file = config.get('refman_data_file') | |
| def _formatting_page_cb(self, formatter: Formatter, page: Page) -> None: | |
| ''' Replace Meson refman tags | |
| Links of the form [[function]] are automatically replaced | |
| with valid links to the correct URL. To reference objects / types use the | |
| [[@object]] syntax. | |
| ''' | |
| for key, value in self._data.items(): | |
| path = os.path.relpath(value, self.app.config.get_invoke_dir()).split('#')[0] | |
| if path == page.link.ref: | |
| if key.startswith('@'): | |
| res = self.create_symbol( | |
| ClassSymbol, | |
| display_name=key[1:], | |
| filename=path, | |
| unique_name=key) | |
| res.link = Link(value, res.display_name, res.unique_name) | |
| elif '.' in key: | |
| res = self.create_symbol( | |
| MethodSymbol, | |
| parameters=[], | |
| display_name=key.split('.')[-1], | |
| parent_name=f'@{key.split(".")[-2]}', | |
| filename=path, | |
| unique_name=key) | |
| res.link = Link(value, key, res.unique_name) | |
| else: | |
| res = self.create_symbol( | |
| FunctionSymbol, | |
| parameters=[], | |
| display_name=key, | |
| filename=path, | |
| unique_name=key) | |
| res.link = Link(value, res.display_name, res.unique_name) | |
| page.symbols.append(res) | |
| link_regex = re.compile(r'(\[\[#?@?([ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\.)*[ \n\t]*[a-zA-Z0-9_]+[ \n\t]*\]\])(.)?', re.MULTILINE) | |
| for m in link_regex.finditer(page.formatted_contents): | |
| i = m.group(1) | |
| obj_id: str = i[2:-2] | |
| obj_id = re.sub(r'[ \n\t]', '', obj_id) # Remove whitespaces | |
| # Marked as inside a code block? | |
| in_code_block = False | |
| if obj_id.startswith('#'): | |
| in_code_block = True | |
| obj_id = obj_id[1:] | |
| if obj_id not in self._data: | |
| warn('unknown-refman-link', f'{Path(page.name).name}: Unknown Meson refman link: "{obj_id}"') | |
| continue | |
| # Just replaces [[!file.id]] paths with the page file (no fancy HTML) | |
| if obj_id.startswith('!'): | |
| page.formatted_contents = page.formatted_contents.replace(i, self._data[obj_id]) | |
| continue | |
| # Fancy links for functions and methods | |
| text = obj_id | |
| if text.startswith('@'): | |
| text = text[1:] | |
| elif in_code_block: | |
| if m.group(3) != '(': | |
| text = text + '()' | |
| else: | |
| text = text + '()' | |
| if not in_code_block: | |
| text = f'<code>{text}</code>' | |
| link = f'<a href="{self._data[obj_id]}"><ins>{text}</ins></a>' | |
| page.formatted_contents = page.formatted_contents.replace(i, link, 1) | |
| def setup(self) -> None: | |
| super().setup() | |
| if not self._data_file: | |
| info('Meson refman extension DISABLED') | |
| return | |
| raw = Path(self._data_file).read_text(encoding='utf-8') | |
| self._data = loads(raw) | |
| # Register formatter | |
| for ext in self.project.extensions.values(): | |
| ext = T.cast(Extension, ext) | |
| ext.formatter.formatting_page_signal.connect(self._formatting_page_cb) | |
| info('Meson refman extension LOADED') | |
| def create_symbol(self, *args, **kwargs): | |
| kwargs['language'] = 'meson' | |
| return super(RefmanLinksExtension, self).create_symbol(*args, **kwargs) | |
| @staticmethod | |
| def get_dependencies() -> T.List[T.Type[Extension]]: | |
| return [] # In case this extension has dependencies on other extensions | |
| def get_extension_classes() -> T.List[T.Type[Extension]]: | |
| return [RefmanLinksExtension] |