You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			258 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			258 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
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)
 |