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 TYPE_CHECKING,
5 Any,
6 Callable,
7 ClassVar,
8 Dict,
9 Generic,
10 Iterable,
11 List,
12 Match,
13 MutableMapping,
14 Optional,
15 Pattern,
16 Set,
17 Type,
18 TypeVar,
19 Union,
20 cast,
21)
23from ._inline.delimiters import DelimiterScanner
25if TYPE_CHECKING:
26 from ._inline.links import Bracket
28DEFAULT_MAX_NESTED_LEVEL = 20
30if sys.version_info >= (3, 11):
31 from typing import Self
32else:
33 from typing_extensions import Self
36class BlockState:
37 """The state to save block parser's cursor and tokens."""
39 src: str
40 tokens: List[Dict[str, Any]]
41 cursor: int
42 cursor_max: int
43 list_tight: bool
44 parent: Any
45 env: MutableMapping[str, Any]
46 lazy_line_starts: Set[int]
48 def __init__(self, parent: Optional[Any] = None) -> None:
49 self.src = ""
50 self.tokens = []
52 # current cursor position
53 self.cursor = 0
54 self.cursor_max = 0
56 # for list and block quote chain
57 self.list_tight = True
58 self.parent = parent
59 self.lazy_line_starts = set()
61 # for saving def references
62 if parent:
63 self.env = parent.env
64 else:
65 self.env = {"ref_links": {}}
67 def child_state(self, src: str, lazy_line_starts: Optional[Set[int]] = None) -> "BlockState":
68 child = self.__class__(self)
69 child.process(src)
70 if lazy_line_starts:
71 child.lazy_line_starts = lazy_line_starts
72 return child
74 def process(self, src: str) -> None:
75 self.src = src
76 self.cursor_max = len(src)
78 def find_line_end(self) -> int:
79 return self.find_line_end_at(self.cursor)
81 def find_line_end_at(self, pos: int) -> int:
82 end = self.src.find("\n", pos)
83 if end < 0:
84 return len(self.src)
85 return end + 1
87 def get_text(self, end_pos: int) -> str:
88 return self.src[self.cursor : end_pos]
90 def get_line(self, start_pos: int) -> str:
91 return self.src[start_pos : self.find_line_end_at(start_pos)]
93 def last_token(self) -> Any:
94 if self.tokens:
95 return self.tokens[-1]
97 def prepend_token(self, token: Dict[str, Any]) -> None:
98 """Insert token before the last token."""
99 self.tokens.insert(len(self.tokens) - 1, token)
101 def append_token(self, token: Dict[str, Any]) -> None:
102 """Add token to the end of token list."""
103 self.tokens.append(token)
105 def add_paragraph(self, text: str) -> None:
106 last_token = self.last_token()
107 if last_token and last_token["type"] == "paragraph":
108 last_token["text"] += text
109 else:
110 self.tokens.append({"type": "paragraph", "text": text})
112 def append_paragraph(self) -> Optional[int]:
113 last_token = self.last_token()
114 if last_token and last_token["type"] == "paragraph":
115 pos = self.find_line_end()
116 last_token["text"] += self.get_text(pos)
117 return pos
118 return None
120 def depth(self) -> int:
121 d = 0
122 parent = self.parent
123 while parent:
124 d += 1
125 parent = parent.parent
126 return d
129class InlineState:
130 """The state to save inline parser's tokens."""
132 def __init__(self, env: MutableMapping[str, Any]):
133 self.env = env
134 self.src = ""
135 self.tokens: List[Dict[str, Any]] = []
136 self.in_image = False
137 self.depth = 0
138 self.in_link = False
139 self.brackets: List["Bracket"] = []
140 self.last_link: Optional["Bracket"] = None
141 self._delimiters: Optional[DelimiterScanner] = None
143 def prepend_token(self, token: Dict[str, Any]) -> None:
144 """Insert token before the last token."""
145 self.tokens.insert(len(self.tokens) - 1, token)
147 def append_token(self, token: Dict[str, Any]) -> None:
148 """Add token to the end of token list."""
149 self.tokens.append(token)
150 if self.brackets and "children" in token:
151 bracket = self.brackets[-1]
152 if "_autolink" not in token or bracket.image:
153 bracket.child_depth = max(bracket.child_depth, token_depth([token]))
155 def copy(self) -> "InlineState":
156 """Create a copy of current state."""
157 state = self.__class__(self.env)
158 state.in_image = self.in_image
159 state.depth = self.depth + 1
160 state.in_link = self.in_link
161 return state
163 @property
164 def delimiters(self) -> DelimiterScanner:
165 if self._delimiters is None or self._delimiters.src is not self.src:
166 self._delimiters = DelimiterScanner(self.src)
167 return self._delimiters
170def token_depth(tokens: List[Dict[str, Any]]) -> int:
171 """Count all inline containers, including mixed plugin and core syntax."""
172 maximum = 0
173 stack = [(token, 0) for token in tokens]
174 while stack:
175 token, depth = stack.pop()
176 if "children" in token:
177 depth += 1
178 maximum = max(maximum, depth)
179 stack.extend((child, depth) for child in token["children"])
180 return maximum
183ST = TypeVar("ST", InlineState, BlockState)
186class Parser(Generic[ST]):
187 sc_flag: "re._FlagsType" = re.M
188 state_cls: Type[ST]
190 SPECIFICATION: ClassVar[Dict[str, str]] = {}
191 DEFAULT_RULES: ClassVar[Iterable[str]] = []
193 def __init__(self) -> None:
194 self.specification = self.SPECIFICATION.copy()
195 self.rules = list(self.DEFAULT_RULES)
196 self._methods: Dict[
197 str,
198 Callable[[Match[str], ST], Optional[int]],
199 ] = {}
201 self.__sc: Dict[str, Pattern[str]] = {}
203 def compile_sc(self, rules: Optional[List[str]] = None) -> Pattern[str]:
204 if rules is None:
205 key = "$"
206 rules = self.rules
207 else:
208 key = "|".join(rules)
210 sc = self.__sc.get(key)
211 if sc:
212 return sc
214 regex = "|".join(r"(?P<%s>%s)" % (k, self.specification[k]) for k in rules)
215 sc = re.compile(regex, self.sc_flag)
216 self.__sc[key] = sc
217 return sc
219 def register(
220 self,
221 name: str,
222 pattern: Union[str, None],
223 func: Callable[[Self, Match[str], ST], Optional[int]],
224 before: Optional[str] = None,
225 ) -> None:
226 """Register a new rule to parse the token. This method is usually used to
227 create a new plugin.
229 :param name: name of the new grammar
230 :param pattern: regex pattern in string
231 :param func: the parsing function
232 :param before: insert this rule before a built-in rule
233 """
234 self._methods[name] = lambda m, state: func(self, m, state)
235 self.__sc.clear()
236 if pattern:
237 self.specification[name] = pattern
238 if name not in self.rules:
239 self.insert_rule(self.rules, name, before=before)
241 def register_rule(self, name: str, pattern: str, func: Any) -> None:
242 raise DeprecationWarning("This plugin is not compatible with mistune v3.")
244 @staticmethod
245 def insert_rule(rules: List[str], name: str, before: Optional[str] = None) -> None:
246 if before:
247 try:
248 index = rules.index(before)
249 rules.insert(index, name)
250 except ValueError:
251 rules.append(name)
252 else:
253 rules.append(name)
255 def parse_method(self, m: Match[str], state: ST) -> Optional[int]:
256 lastgroup = m.lastgroup
257 assert lastgroup
258 func = self._methods[lastgroup]
259 return func(m, state)
262class BaseRenderer(object):
263 NAME: ClassVar[str] = "base"
265 def __init__(self) -> None:
266 self.__methods: Dict[str, Callable[..., str]] = {}
268 def register(self, name: str, method: Callable[..., str]) -> None:
269 """Register a render method for the named token. For example::
271 def render_wiki(renderer, key, title):
272 return f'<a href="/wiki/{key}">{title}</a>'
274 renderer.register('wiki', render_wiki)
275 """
276 # bind self into renderer method
277 self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs)
279 def _get_method(self, name: str) -> Callable[..., str]:
280 try:
281 return cast("Callable[..., str]", object.__getattribute__(self, name))
282 except AttributeError:
283 method = self.__methods.get(name)
284 if not method:
285 raise AttributeError('No renderer "{!r}"'.format(name))
286 return method
288 def render_token(self, token: Dict[str, Any], state: BlockState) -> str:
289 func = self._get_method(token["type"])
290 return func(token, state)
292 def iter_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[str]:
293 for tok in tokens:
294 yield self.render_token(tok, state)
296 def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
297 return "".join(self.iter_tokens(tokens, state))
299 def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str:
300 return self.render_tokens(tokens, state)