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

153 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 Type, 

16 TypeVar, 

17 Union, 

18 cast, 

19) 

20 

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

22 from typing import Self 

23else: 

24 from typing_extensions import Self 

25 

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

27 

28 

29class BlockState: 

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

31 

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] 

39 

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

41 self.src = "" 

42 self.tokens = [] 

43 

44 # current cursor position 

45 self.cursor = 0 

46 self.cursor_max = 0 

47 

48 # for list and block quote chain 

49 self.list_tight = True 

50 self.parent = parent 

51 

52 # for saving def references 

53 if parent: 

54 self.env = parent.env 

55 else: 

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

57 

58 def child_state(self, src: str) -> "BlockState": 

59 child = self.__class__(self) 

60 child.process(src) 

61 return child 

62 

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

64 self.src = src 

65 self.cursor_max = len(src) 

66 

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

71 

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

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

74 

75 def last_token(self) -> Any: 

76 if self.tokens: 

77 return self.tokens[-1] 

78 

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) 

82 

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

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

85 self.tokens.append(token) 

86 

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

93 

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 

101 

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 

109 

110 

111class InlineState: 

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

113 

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 

122 

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) 

126 

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

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

129 self.tokens.append(token) 

130 

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 

139 

140 

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

142 

143 

144class Parser(Generic[ST]): 

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

146 state_cls: Type[ST] 

147 

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

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

150 

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

158 

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

160 

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) 

167 

168 sc = self.__sc.get(key) 

169 if sc: 

170 return sc 

171 

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 

176 

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. 

186 

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) 

197 

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

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

200 

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) 

211 

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) 

217 

218 

219class BaseRenderer(object): 

220 NAME: ClassVar[str] = "base" 

221 

222 def __init__(self) -> None: 

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

224 

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

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

227 

228 def render_wiki(renderer, key, title): 

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

230 

231 renderer.register('wiki', render_wiki) 

232 """ 

233 # bind self into renderer method 

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

235 

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 

244 

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

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

247 return func(token, state) 

248 

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) 

252 

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

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

255 

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

257 return self.render_tokens(tokens, state)