from pathlib import Path from json import loads 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.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. ''' 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'{text}' link = f'{text}' 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') @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]