Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mdit_py_plugins/texmath/index.py: 91%

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

87 statements  

1from __future__ import annotations 

2 

3from collections.abc import Callable, Sequence 

4import re 

5from re import Match 

6from typing import TYPE_CHECKING, Any, TypedDict 

7 

8from markdown_it import MarkdownIt 

9from markdown_it.common.utils import charCodeAt 

10 

11if TYPE_CHECKING: 

12 from markdown_it.renderer import RendererProtocol 

13 from markdown_it.rules_block import StateBlock 

14 from markdown_it.rules_inline import StateInline 

15 from markdown_it.token import Token 

16 from markdown_it.utils import EnvType, OptionsDict 

17 

18 

19def texmath_plugin( 

20 md: MarkdownIt, delimiters: str = "dollars", macros: Any = None 

21) -> None: 

22 """Plugin ported from 

23 `markdown-it-texmath <https://github.com/goessner/markdown-it-texmath>`__. 

24 

25 It parses TeX math equations set inside opening and closing delimiters: 

26 

27 .. code-block:: md 

28 

29 $\\alpha = \\frac{1}{2}$ 

30 

31 :param delimiters: one of: brackets, dollars, gitlab, julia, kramdown 

32 

33 """ 

34 macros = macros or {} 

35 

36 if delimiters in rules: 

37 for rule_inline in rules[delimiters]["inline"]: 

38 md.inline.ruler.before( 

39 "escape", rule_inline["name"], make_inline_func(rule_inline) 

40 ) 

41 

42 def render_math_inline( 

43 self: RendererProtocol, 

44 tokens: Sequence[Token], 

45 idx: int, 

46 options: OptionsDict, 

47 env: EnvType, 

48 ) -> str: 

49 return rule_inline["tmpl"].format( # noqa: B023 

50 render(tokens[idx].content, False, macros) 

51 ) 

52 

53 md.add_render_rule(rule_inline["name"], render_math_inline) 

54 

55 for rule_block in rules[delimiters]["block"]: 

56 md.block.ruler.before( 

57 "fence", rule_block["name"], make_block_func(rule_block) 

58 ) 

59 

60 def render_math_block( 

61 self: RendererProtocol, 

62 tokens: Sequence[Token], 

63 idx: int, 

64 options: OptionsDict, 

65 env: EnvType, 

66 ) -> str: 

67 return rule_block["tmpl"].format( # noqa: B023 

68 render(tokens[idx].content, True, macros), tokens[idx].info 

69 ) 

70 

71 md.add_render_rule(rule_block["name"], render_math_block) 

72 

73 

74class _RuleDictReqType(TypedDict): 

75 name: str 

76 rex: re.Pattern[str] 

77 tmpl: str 

78 tag: str 

79 

80 

81class RuleDictType(_RuleDictReqType, total=False): 

82 # Note in Python 3.10+ could use Req annotation 

83 pre: Any 

84 post: Any 

85 

86 

87def applyRule( 

88 rule: RuleDictType, string: str, begin: int, inBlockquote: bool 

89) -> None | Match[str]: 

90 if not ( 

91 string.startswith(rule["tag"], begin) 

92 and (rule["pre"](string, begin) if "pre" in rule else True) 

93 ): 

94 return None 

95 

96 match = rule["rex"].match(string[begin:]) 

97 

98 if not match or match.start() != 0: 

99 return None 

100 

101 lastIndex = match.end() + begin - 1 

102 if "post" in rule and not ( 

103 rule["post"](string, lastIndex) # valid post-condition 

104 # remove evil blockquote bug (https:#github.com/goessner/mdmath/issues/50) 

105 and (not inBlockquote or "\n" not in match.group(1)) 

106 ): 

107 return None 

108 return match 

109 

110 

111def make_inline_func(rule: RuleDictType) -> Callable[[StateInline, bool], bool]: 

112 def _func(state: StateInline, silent: bool) -> bool: 

113 res = applyRule(rule, state.src, state.pos, False) 

114 if res: 

115 if not silent: 

116 token = state.push(rule["name"], "math", 0) 

117 token.content = res[1] # group 1 from regex .. 

118 token.markup = rule["tag"] 

119 

120 state.pos += res.end() 

121 

122 return bool(res) 

123 

124 return _func 

125 

126 

127def make_block_func(rule: RuleDictType) -> Callable[[StateBlock, int, int, bool], bool]: 

128 def _func(state: StateBlock, begLine: int, endLine: int, silent: bool) -> bool: 

129 begin = state.bMarks[begLine] + state.tShift[begLine] 

130 res = applyRule(rule, state.src, begin, state.parentType == "blockquote") 

131 if res: 

132 if not silent: 

133 token = state.push(rule["name"], "math", 0) 

134 token.block = True 

135 token.content = res[1] 

136 token.info = res[len(res.groups())] 

137 token.markup = rule["tag"] 

138 

139 line = begLine 

140 endpos = begin + res.end() - 1 

141 

142 while line < endLine: 

143 if endpos >= state.bMarks[line] and endpos <= state.eMarks[line]: 

144 # line for end of block math found ... 

145 state.line = line + 1 

146 break 

147 line += 1 

148 

149 return bool(res) 

150 

151 return _func 

152 

153 

154def dollar_pre(src: str, beg: int) -> bool: 

155 prv = charCodeAt(src[beg - 1], 0) if beg > 0 else False 

156 return ( 

157 (not prv) or (prv != 0x5C and (prv < 0x30 or prv > 0x39)) # no backslash, 

158 ) # no decimal digit .. before opening '$' 

159 

160 

161def dollar_post(src: str, end: int) -> bool: 

162 try: 

163 nxt = src[end + 1] and charCodeAt(src[end + 1], 0) 

164 except IndexError: 

165 return True 

166 return ( 

167 (not nxt) or (nxt < 0x30) or (nxt > 0x39) 

168 ) # no decimal digit .. after closing '$' 

169 

170 

171def render(tex: str, displayMode: bool, macros: Any) -> str: 

172 return tex 

173 # TODO better HTML renderer port for math 

174 # try: 

175 # res = katex.renderToString(tex,{throwOnError:False,displayMode,macros}) 

176 # except: 

177 # res = tex+": "+err.message.replace("<","&lt;") 

178 # return res 

179 

180 

181# def use(katex): # math renderer used ... 

182# texmath.katex = katex; # ... katex solely at current ... 

183# return texmath; 

184# } 

185 

186 

187# All regexes areg global (g) and sticky (y), see: 

188# https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky 

189 

190 

191rules: dict[str, dict[str, list[RuleDictType]]] = { 

192 "brackets": { 

193 "inline": [ 

194 { 

195 "name": "math_inline", 

196 "rex": re.compile(r"^\\\((.+?)\\\)", re.DOTALL), 

197 "tmpl": "<eq>{0}</eq>", 

198 "tag": "\\(", 

199 } 

200 ], 

201 "block": [ 

202 { 

203 "name": "math_block_eqno", 

204 "rex": re.compile( 

205 r"^\\\[(((?!\\\]|\\\[)[\s\S])+?)\\\]\s*?\(([^)$\r\n]+?)\)", re.M 

206 ), 

207 "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>', 

208 "tag": "\\[", 

209 }, 

210 { 

211 "name": "math_block", 

212 "rex": re.compile(r"^\\\[([\s\S]+?)\\\]", re.M), 

213 "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n", 

214 "tag": "\\[", 

215 }, 

216 ], 

217 }, 

218 "gitlab": { 

219 "inline": [ 

220 { 

221 "name": "math_inline", 

222 "rex": re.compile(r"^\$`(.+?)`\$"), 

223 "tmpl": "<eq>{0}</eq>", 

224 "tag": "$`", 

225 } 

226 ], 

227 "block": [ 

228 { 

229 "name": "math_block_eqno", 

230 "rex": re.compile( 

231 r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M 

232 ), 

233 "tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', 

234 "tag": "```math", 

235 }, 

236 { 

237 "name": "math_block", 

238 "rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M), 

239 "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n", 

240 "tag": "```math", 

241 }, 

242 ], 

243 }, 

244 "julia": { 

245 "inline": [ 

246 { 

247 "name": "math_inline", 

248 "rex": re.compile(r"^`{2}([^`]+?)`{2}"), 

249 "tmpl": "<eq>{0}</eq>", 

250 "tag": "``", 

251 }, 

252 { 

253 "name": "math_inline", 

254 "rex": re.compile(r"^\$(\S[^$\r\n]*?[^\s\\]{1}?)\$"), 

255 "tmpl": "<eq>{0}</eq>", 

256 "tag": "$", 

257 "pre": dollar_pre, 

258 "post": dollar_post, 

259 }, 

260 { 

261 "name": "math_single", 

262 "rex": re.compile(r"^\$([^$\s\\]{1}?)\$"), 

263 "tmpl": "<eq>{0}</eq>", 

264 "tag": "$", 

265 "pre": dollar_pre, 

266 "post": dollar_post, 

267 }, 

268 ], 

269 "block": [ 

270 { 

271 "name": "math_block_eqno", 

272 "rex": re.compile( 

273 r"^`{3}math\s+?([^`]+?)\s+?`{3}\s*?\(([^)$\r\n]+?)\)", re.M 

274 ), 

275 "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>', 

276 "tag": "```math", 

277 }, 

278 { 

279 "name": "math_block", 

280 "rex": re.compile(r"^`{3}math\s+?([^`]+?)\s+?`{3}", re.M), 

281 "tmpl": "<section><eqn>{0}</eqn></section>", 

282 "tag": "```math", 

283 }, 

284 ], 

285 }, 

286 "kramdown": { 

287 "inline": [ 

288 { 

289 "name": "math_inline", 

290 "rex": re.compile(r"^\${2}([^$\r\n]*?)\${2}"), 

291 "tmpl": "<eq>{0}</eq>", 

292 "tag": "$$", 

293 } 

294 ], 

295 "block": [ 

296 { 

297 "name": "math_block_eqno", 

298 "rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M), 

299 "tmpl": '<section class="eqno"><eqn>{0}</eqn><span>({1})</span></section>', 

300 "tag": "$$", 

301 }, 

302 { 

303 "name": "math_block", 

304 "rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M), 

305 "tmpl": "<section><eqn>{0}</eqn></section>", 

306 "tag": "$$", 

307 }, 

308 ], 

309 }, 

310 "dollars": { 

311 "inline": [ 

312 { 

313 "name": "math_inline", 

314 "rex": re.compile(r"^\$(\S[^$]*?[^\s\\]{1}?)\$"), 

315 "tmpl": "<eq>{0}</eq>", 

316 "tag": "$", 

317 "pre": dollar_pre, 

318 "post": dollar_post, 

319 }, 

320 { 

321 "name": "math_single", 

322 "rex": re.compile(r"^\$([^$\s\\]{1}?)\$"), 

323 "tmpl": "<eq>{0}</eq>", 

324 "tag": "$", 

325 "pre": dollar_pre, 

326 "post": dollar_post, 

327 }, 

328 ], 

329 "block": [ 

330 { 

331 "name": "math_block_eqno", 

332 "rex": re.compile(r"^\${2}([^$]*?)\${2}\s*?\(([^)$\r\n]+?)\)", re.M), 

333 "tmpl": '<section class="eqno">\n<eqn>{0}</eqn><span>({1})</span>\n</section>\n', 

334 "tag": "$$", 

335 }, 

336 { 

337 "name": "math_block", 

338 "rex": re.compile(r"^\${2}([^$]*?)\${2}", re.M), 

339 "tmpl": "<section>\n<eqn>{0}</eqn>\n</section>\n", 

340 "tag": "$$", 

341 }, 

342 ], 

343 }, 

344}