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()
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:]
else:
text = text + '()'
if not in_code_block:
text = f'{text}
'
link = f'{text}'
page.formatted_contents = page.formatted_contents.replace(i, link)
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 formater
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]