Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mistune/plugins/footnotes.py: 28%

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

88 statements  

1import re 

2from typing import TYPE_CHECKING, Any, Dict, List, Match, Union 

3 

4from ..core import BlockState 

5from ..util import unikey 

6 

7if TYPE_CHECKING: 

8 from ..block_parser import BlockParser 

9 from ..core import BaseRenderer, InlineState 

10 from ..inline_parser import InlineParser 

11 from ..markdown import Markdown 

12 

13__all__ = ["footnotes"] 

14 

15_PARAGRAPH_SPLIT = re.compile(r"\n{2,}") 

16# Like LINK_LABEL but disallows whitespace in footnote identifiers 

17# https://michelf.ca/projects/php-markdown/extra/#footnotes 

18_FOOTNOTE_LABEL = r"(?:[^\\\[\]\s]|\\.){1,500}" 

19REF_FOOTNOTE = ( 

20 r"^(?P<footnote_lead> {0,4})" 

21 r"\[\^(?P<footnote_key>" + _FOOTNOTE_LABEL + r")]:[ \t\n]" 

22 r"(?P<footnote_text>[^\n]*(?:\n+|$)" 

23 r"(?:(?P=footnote_lead) {1,4}(?! )[^\n]*\n+)*" 

24 r")" 

25) 

26 

27INLINE_FOOTNOTE = r"\[\^(?P<footnote_key>" + _FOOTNOTE_LABEL + r")\]" 

28 

29 

30def parse_inline_footnote(inline: "InlineParser", m: Match[str], state: "InlineState") -> int: 

31 key = unikey(m.group("footnote_key")) 

32 ref = state.env.get("ref_footnotes") 

33 if ref and key in ref: 

34 notes = state.env.get("footnotes") 

35 if not notes: 

36 notes = [] 

37 if key not in notes: 

38 notes.append(key) 

39 state.env["footnotes"] = notes 

40 state.append_token({"type": "footnote_ref", "raw": key, "attrs": {"index": notes.index(key) + 1}}) 

41 else: 

42 state.append_token({"type": "text", "raw": m.group(0)}) 

43 return m.end() 

44 

45 

46def parse_ref_footnote(block: "BlockParser", m: Match[str], state: BlockState) -> int: 

47 ref = state.env.get("ref_footnotes") 

48 if not ref: 

49 ref = {} 

50 

51 key = unikey(m.group("footnote_key")) 

52 if key not in ref: 

53 ref[key] = m.group("footnote_text") 

54 state.env["ref_footnotes"] = ref 

55 return m.end() 

56 

57 

58def parse_footnote_item(block: "BlockParser", key: str, index: int, state: BlockState) -> Dict[str, Any]: 

59 ref = state.env.get("ref_footnotes") 

60 if not ref: 

61 raise ValueError("Missing 'ref_footnotes'.") 

62 text = ref[key] 

63 

64 lines = text.splitlines() 

65 second_line = None 

66 for second_line in lines[1:]: 

67 if second_line: 

68 break 

69 

70 if second_line: 

71 spaces = len(second_line) - len(second_line.lstrip()) 

72 pattern = re.compile(r"^ {" + str(spaces) + r",}", flags=re.M) 

73 text = pattern.sub("", text).strip() 

74 

75 footer_state = BlockState() 

76 footer_state.process(text) 

77 block.parse(footer_state) 

78 children = footer_state.tokens 

79 else: 

80 text = text.strip() 

81 children = [{"type": "paragraph", "text": text}] 

82 return {"type": "footnote_item", "children": children, "attrs": {"key": key, "index": index}} 

83 

84 

85def md_footnotes_hook( 

86 md: "Markdown", result: Union[str, List[Dict[str, Any]]], state: BlockState 

87) -> Union[str, List[Dict[str, Any]]]: 

88 notes = state.env.get("footnotes") 

89 if not notes: 

90 return result 

91 

92 children = [parse_footnote_item(md.block, k, i + 1, state) for i, k in enumerate(notes)] 

93 state = BlockState(parent=state) 

94 state.tokens = [{"type": "footnotes", "children": children}] 

95 output = md.render_state(state) 

96 return result + output # type: ignore[operator] 

97 

98 

99def render_footnote_ref(renderer: "BaseRenderer", key: str, index: int) -> str: 

100 i = str(index) 

101 html = '<sup class="footnote-ref" id="fnref-' + i + '">' 

102 return html + '<a href="#fn-' + i + '">' + i + "</a></sup>" 

103 

104 

105def render_footnotes(renderer: "BaseRenderer", text: str) -> str: 

106 return '<section class="footnotes">\n<ol>\n' + text + "</ol>\n</section>\n" 

107 

108 

109def render_footnote_item(renderer: "BaseRenderer", text: str, key: str, index: int) -> str: 

110 i = str(index) 

111 back = '<a href="#fnref-' + i + '" class="footnote">&#8617;</a>' 

112 text = text.rstrip() 

113 if text.endswith("</p>"): 

114 text = text[:-4] + back + "</p>" 

115 else: 

116 text = text + "\n" + back 

117 return '<li id="fn-' + i + '">' + text + "</li>\n" 

118 

119 

120def footnotes(md: "Markdown") -> None: 

121 """A mistune plugin to support footnotes, spec defined at 

122 https://michelf.ca/projects/php-markdown/extra/#footnotes 

123 

124 Here is an example: 

125 

126 .. code-block:: text 

127 

128 That's some text with a footnote.[^1] 

129 

130 [^1]: And that's the footnote. 

131 

132 It will be converted into HTML: 

133 

134 .. code-block:: html 

135 

136 <p>That's some text with a footnote.<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup></p> 

137 <section class="footnotes"> 

138 <ol> 

139 <li id="fn-1"><p>And that's the footnote.<a href="#fnref-1" class="footnote">&#8617;</a></p></li> 

140 </ol> 

141 </section> 

142 

143 :param md: Markdown instance 

144 """ 

145 md.inline.register( 

146 "footnote", 

147 INLINE_FOOTNOTE, 

148 parse_inline_footnote, 

149 before="link", 

150 ) 

151 md.block.register( 

152 "ref_footnote", 

153 REF_FOOTNOTE, 

154 parse_ref_footnote, 

155 before="ref_link", 

156 ) 

157 md.after_render_hooks.append(md_footnotes_hook) 

158 

159 if md.renderer and md.renderer.NAME == "html": 

160 md.renderer.register("footnote_ref", render_footnote_ref) 

161 md.renderer.register("footnote_item", render_footnote_item) 

162 md.renderer.register("footnotes", render_footnotes)