| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import re
- import sys
- from typing import (
- Any,
- Callable,
- ClassVar,
- Dict,
- Generic,
- Iterable,
- List,
- Match,
- MutableMapping,
- Optional,
- Pattern,
- Type,
- TypeVar,
- Union,
- cast,
- )
- if sys.version_info >= (3, 11):
- from typing import Self
- else:
- from typing_extensions import Self
- _LINE_END = re.compile(r"\n|$")
- class BlockState:
- """The state to save block parser's cursor and tokens."""
- src: str
- tokens: List[Dict[str, Any]]
- cursor: int
- cursor_max: int
- list_tight: bool
- parent: Any
- env: MutableMapping[str, Any]
- def __init__(self, parent: Optional[Any] = None) -> None:
- self.src = ""
- self.tokens = []
- # current cursor position
- self.cursor = 0
- self.cursor_max = 0
- # for list and block quote chain
- self.list_tight = True
- self.parent = parent
- # for saving def references
- if parent:
- self.env = parent.env
- else:
- self.env = {"ref_links": {}}
- def child_state(self, src: str) -> "BlockState":
- child = self.__class__(self)
- child.process(src)
- return child
- def process(self, src: str) -> None:
- self.src = src
- self.cursor_max = len(src)
- def find_line_end(self) -> int:
- m = _LINE_END.search(self.src, self.cursor)
- assert m is not None
- return m.end()
- def get_text(self, end_pos: int) -> str:
- return self.src[self.cursor : end_pos]
- def last_token(self) -> Any:
- if self.tokens:
- return self.tokens[-1]
- def prepend_token(self, token: Dict[str, Any]) -> None:
- """Insert token before the last token."""
- self.tokens.insert(len(self.tokens) - 1, token)
- def append_token(self, token: Dict[str, Any]) -> None:
- """Add token to the end of token list."""
- self.tokens.append(token)
- def add_paragraph(self, text: str) -> None:
- last_token = self.last_token()
- if last_token and last_token["type"] == "paragraph":
- last_token["text"] += text
- else:
- self.tokens.append({"type": "paragraph", "text": text})
- def append_paragraph(self) -> Optional[int]:
- last_token = self.last_token()
- if last_token and last_token["type"] == "paragraph":
- pos = self.find_line_end()
- last_token["text"] += self.get_text(pos)
- return pos
- return None
- def depth(self) -> int:
- d = 0
- parent = self.parent
- while parent:
- d += 1
- parent = parent.parent
- return d
- class InlineState:
- """The state to save inline parser's tokens."""
- def __init__(self, env: MutableMapping[str, Any]):
- self.env = env
- self.src = ""
- self.tokens: List[Dict[str, Any]] = []
- self.in_image = False
- self.in_link = False
- self.in_emphasis = False
- self.in_strong = False
- def prepend_token(self, token: Dict[str, Any]) -> None:
- """Insert token before the last token."""
- self.tokens.insert(len(self.tokens) - 1, token)
- def append_token(self, token: Dict[str, Any]) -> None:
- """Add token to the end of token list."""
- self.tokens.append(token)
- def copy(self) -> "InlineState":
- """Create a copy of current state."""
- state = self.__class__(self.env)
- state.in_image = self.in_image
- state.in_link = self.in_link
- state.in_emphasis = self.in_emphasis
- state.in_strong = self.in_strong
- return state
- ST = TypeVar("ST", InlineState, BlockState)
- class Parser(Generic[ST]):
- sc_flag: "re._FlagsType" = re.M
- state_cls: Type[ST]
- SPECIFICATION: ClassVar[Dict[str, str]] = {}
- DEFAULT_RULES: ClassVar[Iterable[str]] = []
- def __init__(self) -> None:
- self.specification = self.SPECIFICATION.copy()
- self.rules = list(self.DEFAULT_RULES)
- self._methods: Dict[
- str,
- Callable[[Match[str], ST], Optional[int]],
- ] = {}
- self.__sc: Dict[str, Pattern[str]] = {}
- def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
- if rules is None:
- key = "$"
- rules = self.rules
- else:
- key = "|".join(rules)
- sc = self.__sc.get(key)
- if sc:
- return sc
- regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
- sc = re.compile(regex, self.sc_flag)
- self.__sc[key] = sc
- return sc
- def register(
- self,
- name: str,
- pattern: Union[str, None],
- func: Callable[[Self, Match[str], ST], Optional[int]],
- before: Optional[str] = None,
- ) -> None:
- """Register a new rule to parse the token. This method is usually used to
- create a new plugin.
- :param name: name of the new grammar
- :param pattern: regex pattern in string
- :param func: the parsing function
- :param before: insert this rule before a built-in rule
- """
- self._methods[name] = lambda m, state: func(self, m, state)
- if pattern:
- self.specification[name] = pattern
- if name not in self.rules:
- self.insert_rule(self.rules, name, before=before)
- def register_rule(self, name: str, pattern: str, func: Any) -> None:
- raise DeprecationWarning("This plugin is not compatible with mistune v3.")
- @staticmethod
- def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
- if before:
- try:
- index = rules.index(before)
- rules.insert(index, name)
- except ValueError:
- rules.append(name)
- else:
- rules.append(name)
- def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
- lastgroup = m.lastgroup
- assert lastgroup
- func = self._methods[lastgroup]
- return func(m, state)
- class BaseRenderer(object):
- NAME: ClassVar[str] = "base"
- def __init__(self) -> None:
- self.__methods: Dict[str, Callable[..., str]] = {}
- def register(self, name: str, method: Callable[..., str]) -> None:
- """Register a render method for the named token. For example::
- def render_wiki(renderer, key, title):
- return f'<a href="/wiki/{key}">{title}</a>'
- renderer.register('wiki', render_wiki)
- """
- # bind self into renderer method
- self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)
- def _get_method(self, name: str) -> Callable[..., str]:
- try:
- return cast(Callable[..., str], object.__getattribute__(self, name))
- except AttributeError:
- method = self.__methods.get(name)
- if not method:
- raise AttributeError('No renderer "{!r}"'.format(name))
- return method
- def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
- func = self._get_method(token["type"])
- return func(token, state)
- def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
- for tok in tokens:
- yield self.render_token(tok, state)
- def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
- return "".join(self.iter_tokens(tokens, state))
- def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
- return self.render_tokens(tokens, state)
|