codekingpro/portable-devtools
114k
1import logging2 3from ..common.utils import charCodeAt, isSpace, normalizeReference4from .state_block import StateBlock5 6LOGGER = logging.getLogger(__name__)7 8 9def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool:10 LOGGER.debug(11 "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent12 )13 14 pos = state.bMarks[startLine] + state.tShift[startLine]15 maximum = state.eMarks[startLine]16 nextLine = startLine + 117 18 if state.is_code_block(startLine):19 return False20 21 if state.src[pos] != "[":22 return False23 24 string = state.src[pos : maximum + 1]25 26 # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip()27 maximum = len(string)28 29 labelEnd = None30 pos = 131 while pos < maximum:32 ch = charCodeAt(string, pos)33 if ch == 0x5B: # /* [ */34 return False35 elif ch == 0x5D: # /* ] */36 labelEnd = pos37 break38 elif ch == 0x0A: # /* \n */39 if (lineContent := getNextLine(state, nextLine)) is not None:40 string += lineContent41 maximum = len(string)42 nextLine += 143 elif ch == 0x5C: # /* \ */44 pos += 145 if (46 pos < maximum47 and charCodeAt(string, pos) == 0x0A48 and (lineContent := getNextLine(state, nextLine)) is not None49 ):50 string += lineContent51 maximum = len(string)52 nextLine += 153 pos += 154 55 if (56 labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A57 ): # /* : */58 return False59 60 # [label]: destination 'title'61 # ^^^ skip optional whitespace here62 pos = labelEnd + 263 while pos < maximum:64 ch = charCodeAt(string, pos)65 if ch == 0x0A:66 if (lineContent := getNextLine(state, nextLine)) is not None:67 string += lineContent68 maximum = len(string)69 nextLine += 170 elif isSpace(ch):71 pass72 else:73 break74 pos += 175 76 # [label]: destination 'title'77 # ^^^^^^^^^^^ parse this78 destRes = state.md.helpers.parseLinkDestination(string, pos, maximum)79 if not destRes.ok:80 return False81 82 href = state.md.normalizeLink(destRes.str)83 if not state.md.validateLink(href):84 return False85 86 pos = destRes.pos87 88 # save cursor state, we could require to rollback later89 destEndPos = pos90 destEndLineNo = nextLine91 92 # [label]: destination 'title'93 # ^^^ skipping those spaces94 start = pos95 while pos < maximum:96 ch = charCodeAt(string, pos)97 if ch == 0x0A:98 if (lineContent := getNextLine(state, nextLine)) is not None:99 string += lineContent100 maximum = len(string)101 nextLine += 1102 elif isSpace(ch):103 pass104 else:105 break106 pos += 1107 108 # [label]: destination 'title'109 # ^^^^^^^ parse this110 titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None)111 while titleRes.can_continue:112 if (lineContent := getNextLine(state, nextLine)) is None:113 break114 string += lineContent115 pos = maximum116 maximum = len(string)117 nextLine += 1118 titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes)119 120 if pos < maximum and start != pos and titleRes.ok:121 title = titleRes.str122 pos = titleRes.pos123 else:124 title = ""125 pos = destEndPos126 nextLine = destEndLineNo127 128 # skip trailing spaces until the rest of the line129 while pos < maximum:130 ch = charCodeAt(string, pos)131 if not isSpace(ch):132 break133 pos += 1134 135 if pos < maximum and charCodeAt(string, pos) != 0x0A and title:136 # garbage at the end of the line after title,137 # but it could still be a valid reference if we roll back138 title = ""139 pos = destEndPos140 nextLine = destEndLineNo141 while pos < maximum:142 ch = charCodeAt(string, pos)143 if not isSpace(ch):144 break145 pos += 1146 147 if pos < maximum and charCodeAt(string, pos) != 0x0A:148 # garbage at the end of the line149 return False150 151 label = normalizeReference(string[1:labelEnd])152 if not label:153 # CommonMark 0.20 disallows empty labels154 return False155 156 # Reference can not terminate anything. This check is for safety only.157 if silent:158 return True159 160 if "references" not in state.env:161 state.env["references"] = {}162 163 state.line = nextLine164 165 # note, this is not part of markdown-it JS, but is useful for renderers166 if state.md.options.get("inline_definitions", False):167 token = state.push("definition", "", 0)168 token.meta = {169 "id": label,170 "title": title,171 "url": href,172 "label": string[1:labelEnd],173 }174 token.map = [startLine, state.line]175 176 if label not in state.env["references"]:177 state.env["references"][label] = {178 "title": title,179 "href": href,180 "map": [startLine, state.line],181 }182 else:183 state.env.setdefault("duplicate_refs", []).append(184 {185 "title": title,186 "href": href,187 "label": label,188 "map": [startLine, state.line],189 }190 )191 192 return True193 194 195def getNextLine(state: StateBlock, nextLine: int) -> None | str:196 endLine = state.lineMax197 198 if nextLine >= endLine or state.isEmpty(nextLine):199 # empty line or end of input200 return None201 202 isContinuation = False203 204 # this would be a code block normally, but after paragraph205 # it's considered a lazy continuation regardless of what's there206 if state.is_code_block(nextLine):207 isContinuation = True208 209 # quirk for blockquotes, this line should already be checked by that rule210 if state.sCount[nextLine] < 0:211 isContinuation = True212 213 if not isContinuation:214 terminatorRules = state.md.block.ruler.getRules("reference")215 oldParentType = state.parentType216 state.parentType = "reference"217 218 # Some tags can terminate paragraph without empty line.219 terminate = False220 for terminatorRule in terminatorRules:221 if terminatorRule(state, nextLine, endLine, True):222 terminate = True223 break224 225 state.parentType = oldParentType226 227 if terminate:228 # terminated by another block229 return None230 231 pos = state.bMarks[nextLine] + state.tShift[nextLine]232 maximum = state.eMarks[nextLine]233 234 # max + 1 explicitly includes the newline235 return state.src[pos : maximum + 1]236 