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 Set,
16 Tuple,
17 Type,
18 TypeVar,
19 Union,
20 cast,
21)
23if sys.version_info >= (3, 11):
24 from typing import Self
25else:
26 from typing_extensions import Self
28_LINE_END = re.compile(r"\n|$")
31class BlockState:
32 """The state to save block parser's cursor and tokens."""
34 src: str
35 tokens: List[Dict[str, Any]]
36 cursor: int
37 cursor_max: int
38 list_tight: bool
39 parent: Any
40 env: MutableMapping[str, Any]
41 lazy_line_starts: Set[int]
43 def __init__(self, parent: Optional[Any] = None) -> None:
44 self.src = ""
45 self.tokens = []
47 # current cursor position
48 self.cursor = 0
49 self.cursor_max = 0
51 # for list and block quote chain
52 self.list_tight = True
53 self.parent = parent
54 self.lazy_line_starts = set()
56 # for saving def references
57 if parent:
58 self.env = parent.env
59 else:
60 self.env = {"ref_links": {}}
62 def child_state(self, src: str, lazy_line_starts: Optional[Set[int]] = None) -> "BlockState":
63 child = self.__class__(self)
64 child.process(src)
65 if lazy_line_starts:
66 child.lazy_line_starts = lazy_line_starts
67 return child
69 def process(self, src: str) -> None:
70 self.src = src
71 self.cursor_max = len(src)
73 def find_line_end(self) -> int:
74 return self.find_line_end_at(self.cursor)
76 def find_line_end_at(self, pos: int) -> int:
77 m = _LINE_END.search(self.src, pos)
78 assert m is not None
79 return m.end()
81 def get_text(self, end_pos: int) -> str:
82 return self.src[self.cursor : end_pos]
84 def get_line(self, start_pos: int) -> str:
85 return self.src[start_pos : self.find_line_end_at(start_pos)]
87 def last_token(self) -> Any:
88 if self.tokens:
89 return self.tokens[-1]
91 def prepend_token(self, token: Dict[str, Any]) -> None:
92 """Insert token before the last token."""
93 self.tokens.insert(len(self.tokens) - 1, token)
95 def append_token(self, token: Dict[str, Any]) -> None:
96 """Add token to the end of token list."""
97 self.tokens.append(token)
99 def add_paragraph(self, text: str) -> None:
100 last_token = self.last_token()
101 if last_token and last_token["type"] == "paragraph":
102 last_token["text"] += text
103 else:
104 self.tokens.append({"type": "paragraph", "text": text})
106 def append_paragraph(self) -> Optional[int]:
107 last_token = self.last_token()
108 if last_token and last_token["type"] == "paragraph":
109 pos = self.find_line_end()
110 last_token["text"] += self.get_text(pos)
111 return pos
112 return None
114 def depth(self) -> int:
115 d = 0
116 parent = self.parent
117 while parent:
118 d += 1
119 parent = parent.parent
120 return d
123class InlineState:
124 """The state to save inline parser's tokens."""
126 def __init__(self, env: MutableMapping[str, Any]):
127 self.env = env
128 self.src = ""
129 self.tokens: List[Dict[str, Any]] = []
130 self.in_image = False
131 self.image_depth = 0
132 self.in_link = False
133 self.no_close_bracket_before: int = 0 # high-water mark for DoS mitigation
134 self.no_link_before: int = 0 # high-water mark for failed balanced link candidates
135 self.no_image_before: int = 0 # high-water mark for failed image candidates
136 self.link_brackets: Dict[int, Tuple[str, Dict[int, int]]] = {}
137 self.link_ranges: Dict[int, Tuple[str, List[int], List[int]]] = {}
138 self.formatting_no_end: Dict[Tuple[int, str], Tuple[str, int]] = {}
140 def prepend_token(self, token: Dict[str, Any]) -> None:
141 """Insert token before the last token."""
142 self.tokens.insert(len(self.tokens) - 1, token)
144 def append_token(self, token: Dict[str, Any]) -> None:
145 """Add token to the end of token list."""
146 self.tokens.append(token)
148 def copy(self) -> "InlineState":
149 """Create a copy of current state."""
150 state = self.__class__(self.env)
151 state.in_image = self.in_image
152 state.image_depth = self.image_depth
153 state.in_link = self.in_link
154 state.link_brackets = self.link_brackets
155 state.link_ranges = self.link_ranges
156 state.formatting_no_end = self.formatting_no_end
157 return state
160ST = TypeVar("ST", InlineState, BlockState)
163class Parser(Generic[ST]):
164 sc_flag: "re._FlagsType" = re.M
165 state_cls: Type[ST]
167 SPECIFICATION: ClassVar[Dict[str, str]] = {}
168 DEFAULT_RULES: ClassVar[Iterable[str]] = []
170 def __init__(self) -> None:
171 self.specification = self.SPECIFICATION.copy()
172 self.rules = list(self.DEFAULT_RULES)
173 self._methods: Dict[
174 str,
175 Callable[[Match[str], ST], Optional[int]],
176 ] = {}
178 self.__sc: Dict[str, Pattern[str]] = {}
180 def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
181 if rules is None:
182 key = "$"
183 rules = self.rules
184 else:
185 key = "|".join(rules)
187 sc = self.__sc.get(key)
188 if sc:
189 return sc
191 regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
192 sc = re.compile(regex, self.sc_flag)
193 self.__sc[key] = sc
194 return sc
196 def register(
197 self,
198 name: str,
199 pattern: Union[str, None],
200 func: Callable[[Self, Match[str], ST], Optional[int]],
201 before: Optional[str] = None,
202 ) -> None:
203 """Register a new rule to parse the token. This method is usually used to
204 create a new plugin.
206 :param name: name of the new grammar
207 :param pattern: regex pattern in string
208 :param func: the parsing function
209 :param before: insert this rule before a built-in rule
210 """
211 self._methods[name] = lambda m, state: func(self, m, state)
212 self.__sc.clear()
213 if pattern:
214 self.specification[name] = pattern
215 if name not in self.rules:
216 self.insert_rule(self.rules, name, before=before)
218 def register_rule(self, name: str, pattern: str, func: Any) -> None:
219 raise DeprecationWarning("This plugin is not compatible with mistune v3.")
221 @staticmethod
222 def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
223 if before:
224 try:
225 index = rules.index(before)
226 rules.insert(index, name)
227 except ValueError:
228 rules.append(name)
229 else:
230 rules.append(name)
232 def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
233 lastgroup = m.lastgroup
234 assert lastgroup
235 func = self._methods[lastgroup]
236 return func(m, state)
239class BaseRenderer(object):
240 NAME: ClassVar[str] = "base"
242 def __init__(self) -> None:
243 self.__methods: Dict[str, Callable[..., str]] = {}
245 def register(self, name: str, method: Callable[..., str]) -> None:
246 """Register a render method for the named token. For example::
248 def render_wiki(renderer, key, title):
249 return f'<a href="/wiki/{key}">{title}</a>'
251 renderer.register('wiki', render_wiki)
252 """
253 # bind self into renderer method
254 self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)
256 def _get_method(self, name: str) -> Callable[..., str]:
257 try:
258 return cast(Callable[..., str], object.__getattribute__(self, name))
259 except AttributeError:
260 method = self.__methods.get(name)
261 if not method:
262 raise AttributeError('No renderer "{!r}"'.format(name))
263 return method
265 def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
266 func = self._get_method(token["type"])
267 return func(token, state)
269 def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
270 for tok in tokens:
271 yield self.render_token(tok, state)
273 def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
274 return "".join(self.iter_tokens(tokens, state))
276 def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
277 return self.render_tokens(tokens, state)