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