File size: 4,605 Bytes
5f5a27c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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()