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

169 statements  

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) 

22 

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

24 from typing import Self 

25else: 

26 from typing_extensions import Self 

27 

28_LINE_END = re.compile(r"\n|$") 

29 

30 

31class BlockState: 

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

33 

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] 

42 

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

44 self.src = "" 

45 self.tokens = [] 

46 

47 # current cursor position 

48 self.cursor = 0 

49 self.cursor_max = 0 

50 

51 # for list and block quote chain 

52 self.list_tight = True 

53 self.parent = parent 

54 self.lazy_line_starts = set() 

55 

56 # for saving def references 

57 if parent: 

58 self.env = parent.env 

59 else: 

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

61 

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 

68 

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

70 self.src = src 

71 self.cursor_max = len(src) 

72 

73 def find_line_end(self) -> int: 

74 return self.find_line_end_at(self.cursor) 

75 

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

80 

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

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

83 

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

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

86 

87 def last_token(self) -> Any: 

88 if self.tokens: 

89 return self.tokens[-1] 

90 

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) 

94 

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

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

97 self.tokens.append(token) 

98 

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

105 

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 

113 

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 

121 

122 

123class InlineState: 

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

125 

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

139 

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) 

143 

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

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

146 self.tokens.append(token) 

147 

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 

158 

159 

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

161 

162 

163class Parser(Generic[ST]): 

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

165 state_cls: Type[ST] 

166 

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

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

169 

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

177 

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

179 

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) 

186 

187 sc = self.__sc.get(key) 

188 if sc: 

189 return sc 

190 

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 

195 

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. 

205 

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) 

217 

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

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

220 

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) 

231 

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) 

237 

238 

239class BaseRenderer(object): 

240 NAME: ClassVar[str] = "base" 

241 

242 def __init__(self) -> None: 

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

244 

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

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

247 

248 def render_wiki(renderer, key, title): 

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

250 

251 renderer.register('wiki', render_wiki) 

252 """ 

253 # bind self into renderer method 

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

255 

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 

264 

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

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

267 return func(token, state) 

268 

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) 

272 

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

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

275 

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

277 return self.render_tokens(tokens, state)