Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/core.py: 96%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
2import sys
3from typing import (
4 Any,
5 Callable,
6 ClassVar,
7 Dict,
8 Generic,
9 Iterable,
10 List,
11 Match,
12 MutableMapping,
13 Optional,
14 Pattern,
15 Type,
16 TypeVar,
17 Union,
18 cast,
19)
21if sys.version_info >= (3, 11):
22 from typing import Self
23else:
24 from typing_extensions import Self
26_LINE_END = re.compile(r"\n|$")
29class BlockState:
30 """The state to save block parser's cursor and tokens."""
32 src: str
33 tokens: List[Dict[str, Any]]
34 cursor: int
35 cursor_max: int
36 list_tight: bool
37 parent: Any
38 env: MutableMapping[str, Any]
40 def __init__(self, parent: Optional[Any] = None) -> None:
41 self.src = ""
42 self.tokens = []
44 # current cursor position
45 self.cursor = 0
46 self.cursor_max = 0
48 # for list and block quote chain
49 self.list_tight = True
50 self.parent = parent
52 # for saving def references
53 if parent:
54 self.env = parent.env
55 else:
56 self.env = {"ref_links": {}}
58 def child_state(self, src: str) -> "BlockState":
59 child = self.__class__(self)
60 child.process(src)
61 return child
63 def process(self, src: str) -> None:
64 self.src = src
65 self.cursor_max = len(src)
67 def find_line_end(self) -> int:
68 m = _LINE_END.search(self.src, self.cursor)
69 assert m is not None
70 return m.end()
72 def get_text(self, end_pos: int) -> str:
73 return self.src[self.cursor : end_pos]
75 def last_token(self) -> Any:
76 if self.tokens:
77 return self.tokens[-1]
79 def prepend_token(self, token: Dict[str, Any]) -> None:
80 """Insert token before the last token."""
81 self.tokens.insert(len(self.tokens) - 1, token)
83 def append_token(self, token: Dict[str, Any]) -> None:
84 """Add token to the end of token list."""
85 self.tokens.append(token)
87 def add_paragraph(self, text: str) -> None:
88 last_token = self.last_token()
89 if last_token and last_token["type"] == "paragraph":
90 last_token["text"] += text
91 else:
92 self.tokens.append({"type": "paragraph", "text": text})
94 def append_paragraph(self) -> Optional[int]:
95 last_token = self.last_token()
96 if last_token and last_token["type"] == "paragraph":
97 pos = self.find_line_end()
98 last_token["text"] += self.get_text(pos)
99 return pos
100 return None
102 def depth(self) -> int:
103 d = 0
104 parent = self.parent
105 while parent:
106 d += 1
107 parent = parent.parent
108 return d
111class InlineState:
112 """The state to save inline parser's tokens."""
114 def __init__(self, env: MutableMapping[str, Any]):
115 self.env = env
116 self.src = ""
117 self.tokens: List[Dict[str, Any]] = []
118 self.in_image = False
119 self.in_link = False
120 self.in_emphasis = False
121 self.in_strong = False
123 def prepend_token(self, token: Dict[str, Any]) -> None:
124 """Insert token before the last token."""
125 self.tokens.insert(len(self.tokens) - 1, token)
127 def append_token(self, token: Dict[str, Any]) -> None:
128 """Add token to the end of token list."""
129 self.tokens.append(token)
131 def copy(self) -> "InlineState":
132 """Create a copy of current state."""
133 state = self.__class__(self.env)
134 state.in_image = self.in_image
135 state.in_link = self.in_link
136 state.in_emphasis = self.in_emphasis
137 state.in_strong = self.in_strong
138 return state
141ST = TypeVar("ST", InlineState, BlockState)
144class Parser(Generic[ST]):
145 sc_flag: "re._FlagsType" = re.M
146 state_cls: Type[ST]
148 SPECIFICATION: ClassVar[Dict[str, str]] = {}
149 DEFAULT_RULES: ClassVar[Iterable[str]] = []
151 def __init__(self) -> None:
152 self.specification = self.SPECIFICATION.copy()
153 self.rules = list(self.DEFAULT_RULES)
154 self._methods: Dict[
155 str,
156 Callable[[Match[str], ST], Optional[int]],
157 ] = {}
159 self.__sc: Dict[str, Pattern[str]] = {}
161 def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
162 if rules is None:
163 key = "$"
164 rules = self.rules
165 else:
166 key = "|".join(rules)
168 sc = self.__sc.get(key)
169 if sc:
170 return sc
172 regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
173 sc = re.compile(regex, self.sc_flag)
174 self.__sc[key] = sc
175 return sc
177 def register(
178 self,
179 name: str,
180 pattern: Union[str, None],
181 func: Callable[[Self, Match[str], ST], Optional[int]],
182 before: Optional[str] = None,
183 ) -> None:
184 """Register a new rule to parse the token. This method is usually used to
185 create a new plugin.
187 :param name: name of the new grammar
188 :param pattern: regex pattern in string
189 :param func: the parsing function
190 :param before: insert this rule before a built-in rule
191 """
192 self._methods[name] = lambda m, state: func(self, m, state)
193 if pattern:
194 self.specification[name] = pattern
195 if name not in self.rules:
196 self.insert_rule(self.rules, name, before=before)
198 def register_rule(self, name: str, pattern: str, func: Any) -> None:
199 raise DeprecationWarning("This plugin is not compatible with mistune v3.")
201 @staticmethod
202 def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
203 if before:
204 try:
205 index = rules.index(before)
206 rules.insert(index, name)
207 except ValueError:
208 rules.append(name)
209 else:
210 rules.append(name)
212 def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
213 lastgroup = m.lastgroup
214 assert lastgroup
215 func = self._methods[lastgroup]
216 return func(m, state)
219class BaseRenderer(object):
220 NAME: ClassVar[str] = "base"
222 def __init__(self) -> None:
223 self.__methods: Dict[str, Callable[..., str]] = {}
225 def register(self, name: str, method: Callable[..., str]) -> None:
226 """Register a render method for the named token. For example::
228 def render_wiki(renderer, key, title):
229 return f'<a href="/wiki/{key}">{title}</a>'
231 renderer.register('wiki', render_wiki)
232 """
233 # bind self into renderer method
234 self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)
236 def _get_method(self, name: str) -> Callable[..., str]:
237 try:
238 return cast(Callable[..., str], object.__getattribute__(self, name))
239 except AttributeError:
240 method = self.__methods.get(name)
241 if not method:
242 raise AttributeError('No renderer "{!r}"'.format(name))
243 return method
245 def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
246 func = self._get_method(token["type"])
247 return func(token, state)
249 def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
250 for tok in tokens:
251 yield self.render_token(tok, state)
253 def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
254 return "".join(self.iter_tokens(tokens, state))
256 def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
257 return self.render_tokens(tokens, state)