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

186 statements  

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) 

22 

23from ._inline.delimiters import DelimiterScanner 

24 

25if TYPE_CHECKING: 

26 from ._inline.links import Bracket 

27 

28DEFAULT_MAX_NESTED_LEVEL = 20 

29 

30if sys.version_info >= (3, 11): 

31 from typing import Self 

32else: 

33 from typing_extensions import Self 

34 

35 

36class BlockState: 

37 """The state to save block parser's cursor and tokens.""" 

38 

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] 

47 

48 def __init__(self, parent: Optional[Any] = None) -> None: 

49 self.src = "" 

50 self.tokens = [] 

51 

52 # current cursor position 

53 self.cursor = 0 

54 self.cursor_max = 0 

55 

56 # for list and block quote chain 

57 self.list_tight = True 

58 self.parent = parent 

59 self.lazy_line_starts = set() 

60 

61 # for saving def references 

62 if parent: 

63 self.env = parent.env 

64 else: 

65 self.env = {"ref_links": {}} 

66 

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 

73 

74 def process(self, src: str) -> None: 

75 self.src = src 

76 self.cursor_max = len(src) 

77 

78 def find_line_end(self) -> int: 

79 return self.find_line_end_at(self.cursor) 

80 

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 

86 

87 def get_text(self, end_pos: int) -> str: 

88 return self.src[self.cursor : end_pos] 

89 

90 def get_line(self, start_pos: int) -> str: 

91 return self.src[start_pos : self.find_line_end_at(start_pos)] 

92 

93 def last_token(self) -> Any: 

94 if self.tokens: 

95 return self.tokens[-1] 

96 

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) 

100 

101 def append_token(self, token: Dict[str, Any]) -> None: 

102 """Add token to the end of token list.""" 

103 self.tokens.append(token) 

104 

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}) 

111 

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 

119 

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 

127 

128 

129class InlineState: 

130 """The state to save inline parser's tokens.""" 

131 

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 

142 

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) 

146 

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])) 

154 

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 

162 

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 

168 

169 

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 

181 

182 

183ST = TypeVar("ST", InlineState, BlockState) 

184 

185 

186class Parser(Generic[ST]): 

187 sc_flag: "re._FlagsType" = re.M 

188 state_cls: Type[ST] 

189 

190 SPECIFICATION: ClassVar[Dict[str, str]] = {} 

191 DEFAULT_RULES: ClassVar[Iterable[str]] = [] 

192 

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 ] = {} 

200 

201 self.__sc: Dict[str, Pattern[str]] = {} 

202 

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) 

209 

210 sc = self.__sc.get(key) 

211 if sc: 

212 return sc 

213 

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 

218 

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. 

228 

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) 

240 

241 def register_rule(self, name: str, pattern: str, func: Any) -> None: 

242 raise DeprecationWarning("This plugin is not compatible with mistune v3.") 

243 

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) 

254 

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) 

260 

261 

262class BaseRenderer(object): 

263 NAME: ClassVar[str] = "base" 

264 

265 def __init__(self) -> None: 

266 self.__methods: Dict[str, Callable[..., str]] = {} 

267 

268 def register(self, name: str, method: Callable[..., str]) -> None: 

269 """Register a render method for the named token. For example:: 

270 

271 def render_wiki(renderer, key, title): 

272 return f'<a href="/wiki/{key}">{title}</a>' 

273 

274 renderer.register('wiki', render_wiki) 

275 """ 

276 # bind self into renderer method 

277 self.__methods[name] = lambda *arg, **kwargs: method(self, *arg, **kwargs) 

278 

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 

287 

288 def render_token(self, token: Dict[str, Any], state: BlockState) -> str: 

289 func = self._get_method(token["type"]) 

290 return func(token, state) 

291 

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) 

295 

296 def render_tokens(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str: 

297 return "".join(self.iter_tokens(tokens, state)) 

298 

299 def __call__(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> str: 

300 return self.render_tokens(tokens, state)