Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_block/reference.py: 94%
131 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
1import logging
3from ..common.utils import charCodeAt, isSpace, normalizeReference
4from .state_block import StateBlock
6LOGGER = logging.getLogger(__name__)
9def reference(state: StateBlock, startLine, _endLine, silent):
10 LOGGER.debug(
11 "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent
12 )
14 lines = 0
15 pos = state.bMarks[startLine] + state.tShift[startLine]
16 maximum = state.eMarks[startLine]
17 nextLine = startLine + 1
19 # if it's indented more than 3 spaces, it should be a code block
20 if state.sCount[startLine] - state.blkIndent >= 4:
21 return False
23 if state.srcCharCode[pos] != 0x5B: # /* [ */
24 return False
26 # Simple check to quickly interrupt scan on [link](url) at the start of line.
27 # Can be useful on practice: https:#github.com/markdown-it/markdown-it/issues/54
28 while pos < maximum:
29 # /* ] */ /* \ */ /* : */
30 if state.srcCharCode[pos] == 0x5D and state.srcCharCode[pos - 1] != 0x5C:
31 if pos + 1 == maximum:
32 return False
33 if state.srcCharCode[pos + 1] != 0x3A:
34 return False
35 break
36 pos += 1
38 endLine = state.lineMax
40 # jump line-by-line until empty one or EOF
41 terminatorRules = state.md.block.ruler.getRules("reference")
43 oldParentType = state.parentType
44 state.parentType = "reference"
46 while nextLine < endLine and not state.isEmpty(nextLine):
47 # this would be a code block normally, but after paragraph
48 # it's considered a lazy continuation regardless of what's there
49 if state.sCount[nextLine] - state.blkIndent > 3:
50 nextLine += 1
51 continue
53 # quirk for blockquotes, this line should already be checked by that rule
54 if state.sCount[nextLine] < 0:
55 nextLine += 1
56 continue
58 # Some tags can terminate paragraph without empty line.
59 terminate = False
60 for terminatorRule in terminatorRules:
61 if terminatorRule(state, nextLine, endLine, True):
62 terminate = True
63 break
65 if terminate:
66 break
68 nextLine += 1
70 string = state.getLines(startLine, nextLine, state.blkIndent, False).strip()
71 maximum = len(string)
73 labelEnd = None
74 pos = 1
75 while pos < maximum:
76 ch = charCodeAt(string, pos)
77 if ch == 0x5B: # /* [ */
78 return False
79 elif ch == 0x5D: # /* ] */
80 labelEnd = pos
81 break
82 elif ch == 0x0A: # /* \n */
83 lines += 1
84 elif ch == 0x5C: # /* \ */
85 pos += 1
86 if pos < maximum and charCodeAt(string, pos) == 0x0A:
87 lines += 1
88 pos += 1
90 if (
91 labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A
92 ): # /* : */
93 return False
95 # [label]: destination 'title'
96 # ^^^ skip optional whitespace here
97 pos = labelEnd + 2
98 while pos < maximum:
99 ch = charCodeAt(string, pos)
100 if ch == 0x0A:
101 lines += 1
102 elif isSpace(ch):
103 pass
104 else:
105 break
106 pos += 1
108 # [label]: destination 'title'
109 # ^^^^^^^^^^^ parse this
110 res = state.md.helpers.parseLinkDestination(string, pos, maximum)
111 if not res.ok:
112 return False
114 href = state.md.normalizeLink(res.str)
115 if not state.md.validateLink(href):
116 return False
118 pos = res.pos
119 lines += res.lines
121 # save cursor state, we could require to rollback later
122 destEndPos = pos
123 destEndLineNo = lines
125 # [label]: destination 'title'
126 # ^^^ skipping those spaces
127 start = pos
128 while pos < maximum:
129 ch = charCodeAt(string, pos)
130 if ch == 0x0A:
131 lines += 1
132 elif isSpace(ch):
133 pass
134 else:
135 break
136 pos += 1
138 # [label]: destination 'title'
139 # ^^^^^^^ parse this
140 res = state.md.helpers.parseLinkTitle(string, pos, maximum)
141 if pos < maximum and start != pos and res.ok:
142 title = res.str
143 pos = res.pos
144 lines += res.lines
145 else:
146 title = ""
147 pos = destEndPos
148 lines = destEndLineNo
150 # skip trailing spaces until the rest of the line
151 while pos < maximum:
152 ch = charCodeAt(string, pos)
153 if not isSpace(ch):
154 break
155 pos += 1
157 if pos < maximum and charCodeAt(string, pos) != 0x0A:
158 if title:
159 # garbage at the end of the line after title,
160 # but it could still be a valid reference if we roll back
161 title = ""
162 pos = destEndPos
163 lines = destEndLineNo
164 while pos < maximum:
165 ch = charCodeAt(string, pos)
166 if not isSpace(ch):
167 break
168 pos += 1
170 if pos < maximum and charCodeAt(string, pos) != 0x0A:
171 # garbage at the end of the line
172 return False
174 label = normalizeReference(string[1:labelEnd])
175 if not label:
176 # CommonMark 0.20 disallows empty labels
177 return False
179 # Reference can not terminate anything. This check is for safety only.
180 if silent:
181 return True
183 if "references" not in state.env:
184 state.env["references"] = {}
186 state.line = startLine + lines + 1
188 # note, this is not part of markdown-it JS, but is useful for renderers
189 if state.md.options.get("inline_definitions", False):
190 token = state.push("definition", "", 0)
191 token.meta = {
192 "id": label,
193 "title": title,
194 "url": href,
195 "label": string[1:labelEnd],
196 }
197 token.map = [startLine, state.line]
199 if label not in state.env["references"]:
200 state.env["references"][label] = {
201 "title": title,
202 "href": href,
203 "map": [startLine, state.line],
204 }
205 else:
206 state.env.setdefault("duplicate_refs", []).append(
207 {
208 "title": title,
209 "href": href,
210 "label": label,
211 "map": [startLine, state.line],
212 }
213 )
215 state.parentType = oldParentType
217 return True