163 أسطر
4.5 KiB
Python
163 أسطر
4.5 KiB
Python
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()
|