| import re |
| from abc import ABCMeta, abstractmethod |
| from typing import ( |
| TYPE_CHECKING, |
| Any, |
| Callable, |
| Dict, |
| Iterable, |
| List, |
| Match, |
| Optional, |
| Tuple, |
| Type, |
| Union, |
| ) |
|
|
| if TYPE_CHECKING: |
| from ..block_parser import BlockParser |
| from ..core import BlockState |
| from ..markdown import Markdown |
|
|
|
|
| class DirectiveParser(ABCMeta): |
| name = "directive" |
|
|
| @staticmethod |
| @abstractmethod |
| def parse_type(m: Match[str]) -> str: |
| raise NotImplementedError() |
|
|
| @staticmethod |
| @abstractmethod |
| def parse_title(m: Match[str]) -> str: |
| raise NotImplementedError() |
|
|
| @staticmethod |
| @abstractmethod |
| def parse_content(m: Match[str]) -> str: |
| raise NotImplementedError() |
|
|
| @classmethod |
| def parse_tokens(cls, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]: |
| if state.depth() >= block.max_nested_level - 1 and cls.name in block.rules: |
| rules = list(block.rules) |
| rules.remove(cls.name) |
| else: |
| rules = block.rules |
| child = state.child_state(text) |
| block.parse(child, rules) |
| return child.tokens |
|
|
| @staticmethod |
| def parse_options(m: Match[str]) -> List[Tuple[str, str]]: |
| text = m.group("options") |
| if not text.strip(): |
| return [] |
|
|
| options = [] |
| for line in re.split(r"\n+", text): |
| line = line.strip()[1:] |
| if not line: |
| continue |
| i = line.find(":") |
| k = line[:i] |
| v = line[i + 1 :].strip() |
| options.append((k, v)) |
| return options |
|
|
|
|
| class BaseDirective(metaclass=ABCMeta): |
| parser: Type[DirectiveParser] |
| directive_pattern: Optional[str] = None |
|
|
| def __init__(self, plugins: List["DirectivePlugin"]): |
| self._methods: Dict[ |
| str, |
| Callable[ |
| ["BlockParser", Match[str], "BlockState"], |
| Union[Dict[str, Any], List[Dict[str, Any]]], |
| ], |
| ] = {} |
| self.__plugins = plugins |
|
|
| def register( |
| self, |
| name: str, |
| fn: Callable[ |
| ["BlockParser", Match[str], "BlockState"], |
| Union[Dict[str, Any], List[Dict[str, Any]]], |
| ], |
| ) -> None: |
| self._methods[name] = fn |
|
|
| def parse_method( |
| self, block: "BlockParser", m: Match[str], state: "BlockState" |
| ) -> Union[Dict[str, Any], List[Dict[str, Any]]]: |
| _type = self.parser.parse_type(m) |
| method = self._methods.get(_type) |
| if method: |
| try: |
| token = method(block, m, state) |
| except ValueError as e: |
| token = {"type": "block_error", "raw": str(e)} |
| else: |
| text = m.group(0) |
| token = { |
| "type": "block_error", |
| "raw": text, |
| } |
|
|
| if isinstance(token, list): |
| for tok in token: |
| state.append_token(tok) |
| else: |
| state.append_token(token) |
| return token |
|
|
| @abstractmethod |
| def parse_directive(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]: |
| raise NotImplementedError() |
|
|
| def register_block_parser(self, md: "Markdown", before: Optional[str] = None) -> None: |
| md.block.register( |
| self.parser.name, |
| self.directive_pattern, |
| self.parse_directive, |
| before=before, |
| ) |
|
|
| def __call__(self, markdown: "Markdown") -> None: |
| for plugin in self.__plugins: |
| plugin.parser = self.parser |
| plugin(self, markdown) |
|
|
|
|
| class DirectivePlugin: |
| parser: Type[DirectiveParser] |
|
|
| def __init__(self) -> None: ... |
|
|
| def parse_options(self, m: Match[str]) -> List[Tuple[str, str]]: |
| return self.parser.parse_options(m) |
|
|
| def parse_type(self, m: Match[str]) -> str: |
| return self.parser.parse_type(m) |
|
|
| def parse_title(self, m: Match[str]) -> str: |
| return self.parser.parse_title(m) |
|
|
| def parse_content(self, m: Match[str]) -> str: |
| return self.parser.parse_content(m) |
|
|
| def parse_tokens(self, block: "BlockParser", text: str, state: "BlockState") -> Iterable[Dict[str, Any]]: |
| return self.parser.parse_tokens(block, text, state) |
|
|
| def parse( |
| self, block: "BlockParser", m: Match[str], state: "BlockState" |
| ) -> Union[Dict[str, Any], List[Dict[str, Any]]]: |
| raise NotImplementedError() |
|
|
| def __call__(self, directive: BaseDirective, md: "Markdown") -> None: |
| raise NotImplementedError() |
|
|