Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

85 statements  

1from __future__ import annotations 

2 

3import re 

4from typing import TYPE_CHECKING, Any, Callable, Match, Sequence, TypedDict 

5 

6from markdown_it import MarkdownIt 

7from markdown_it.common.utils import charCodeAt 

8 

9if TYPE_CHECKING: 

10 from markdown_it.renderer import RendererProtocol 

11 from markdown_it.rules_block import StateBlock 

12 from markdown_it.rules_inline import StateInline 

13 from markdown_it.token import Token 

14 from markdown_it.utils import EnvType, OptionsDict 

15 

16 

17def texmath_plugin( 

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

19) -> None: 

20 """Plugin ported from 

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

22 

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

24 

25 .. code-block:: md 

26 

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

28 

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

30 

31 """ 

32 macros = macros or {} 

33 

34 if delimiters in rules: 

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

36 md.inline.ruler.before( 

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

38 ) 

39 

40 def render_math_inline( 

41 self: RendererProtocol, 

42 tokens: Sequence[Token], 

43 idx: int, 

44 options: OptionsDict, 

45 env: EnvType, 

46 ) -> str: 

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

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

49 ) 

50 

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

52 

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

54 md.block.ruler.before( 

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

56 ) 

57 

58 def render_math_block( 

59 self: RendererProtocol, 

60 tokens: Sequence[Token], 

61 idx: int, 

62 options: OptionsDict, 

63 env: EnvType, 

64 ) -> str: 

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

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

67 ) 

68 

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

70 

71 

72class _RuleDictReqType(TypedDict): 

73 name: str 

74 rex: re.Pattern[str] 

75 tmpl: str 

76 tag: str 

77 

78 

79class RuleDictType(_RuleDictReqType, total=False): 

80 # Note in Python 3.10+ could use Req annotation 

81 pre: Any 

82 post: Any 

83 

84 

85def applyRule( 

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

87) -> None | Match[str]: 

88 if not ( 

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

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

91 ): 

92 return None 

93 

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

95 

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

97 return None 

98 

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

100 if "post" in rule and not ( 

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

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

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

104 ): 

105 return None 

106 return match 

107 

108 

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

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

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

112 if res: 

113 if not silent: 

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

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

116 token.markup = rule["tag"] 

117 

118 state.pos += res.end() 

119 

120 return bool(res) 

121 

122 return _func 

123 

124 

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

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

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

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

129 if res: 

130 if not silent: 

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

132 token.block = True 

133 token.content = res[1] 

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

135 token.markup = rule["tag"] 

136 

137 line = begLine 

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

139 

140 while line < endLine: 

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

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

143 state.line = line + 1 

144 break 

145 line += 1 

146 

147 return bool(res) 

148 

149 return _func 

150 

151 

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

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

154 return ( 

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

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

157 

158 

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

160 try: 

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

162 except IndexError: 

163 return True 

164 return ( 

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

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

167 

168 

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

170 return tex 

171 # TODO better HTML renderer port for math 

172 # try: 

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

174 # except: 

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

176 # return res 

177 

178 

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

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

181# return texmath; 

182# } 

183 

184 

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

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

187 

188 

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

190 "brackets": { 

191 "inline": [ 

192 { 

193 "name": "math_inline", 

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

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

196 "tag": "\\(", 

197 } 

198 ], 

199 "block": [ 

200 { 

201 "name": "math_block_eqno", 

202 "rex": re.compile( 

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

204 ), 

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

206 "tag": "\\[", 

207 }, 

208 { 

209 "name": "math_block", 

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

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

212 "tag": "\\[", 

213 }, 

214 ], 

215 }, 

216 "gitlab": { 

217 "inline": [ 

218 { 

219 "name": "math_inline", 

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

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

222 "tag": "$`", 

223 } 

224 ], 

225 "block": [ 

226 { 

227 "name": "math_block_eqno", 

228 "rex": re.compile( 

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

230 ), 

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

232 "tag": "```math", 

233 }, 

234 { 

235 "name": "math_block", 

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

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

238 "tag": "```math", 

239 }, 

240 ], 

241 }, 

242 "julia": { 

243 "inline": [ 

244 { 

245 "name": "math_inline", 

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

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

248 "tag": "``", 

249 }, 

250 { 

251 "name": "math_inline", 

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

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

254 "tag": "$", 

255 "pre": dollar_pre, 

256 "post": dollar_post, 

257 }, 

258 { 

259 "name": "math_single", 

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

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

262 "tag": "$", 

263 "pre": dollar_pre, 

264 "post": dollar_post, 

265 }, 

266 ], 

267 "block": [ 

268 { 

269 "name": "math_block_eqno", 

270 "rex": re.compile( 

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

272 ), 

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

274 "tag": "```math", 

275 }, 

276 { 

277 "name": "math_block", 

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

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

280 "tag": "```math", 

281 }, 

282 ], 

283 }, 

284 "kramdown": { 

285 "inline": [ 

286 { 

287 "name": "math_inline", 

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

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

290 "tag": "$$", 

291 } 

292 ], 

293 "block": [ 

294 { 

295 "name": "math_block_eqno", 

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

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

298 "tag": "$$", 

299 }, 

300 { 

301 "name": "math_block", 

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

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

304 "tag": "$$", 

305 }, 

306 ], 

307 }, 

308 "dollars": { 

309 "inline": [ 

310 { 

311 "name": "math_inline", 

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

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

314 "tag": "$", 

315 "pre": dollar_pre, 

316 "post": dollar_post, 

317 }, 

318 { 

319 "name": "math_single", 

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

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

322 "tag": "$", 

323 "pre": dollar_pre, 

324 "post": dollar_post, 

325 }, 

326 ], 

327 "block": [ 

328 { 

329 "name": "math_block_eqno", 

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

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

332 "tag": "$$", 

333 }, 

334 { 

335 "name": "math_block", 

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

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

338 "tag": "$$", 

339 }, 

340 ], 

341 }, 

342}