summaryrefslogtreecommitdiffstats
path: root/contrib/python/markdown-it-py/markdown_it/rules_block/reference.py
diff options
context:
space:
mode:
authoreivanov89 <[email protected]>2025-08-29 10:12:02 +0300
committereivanov89 <[email protected]>2025-08-29 10:27:27 +0300
commit140ced4d34c422c9f3cbe096f8dd35243b67d6e4 (patch)
treeb7373341f64151c0ab9839ee692dc919366590d5 /contrib/python/markdown-it-py/markdown_it/rules_block/reference.py
parent136471c8b2f3ab8cd7993200c0de0456b7018118 (diff)
Add python/textual to YDB
commit_hash:eda16a869229724fec5479fa27fa5cdbccbe0395
Diffstat (limited to 'contrib/python/markdown-it-py/markdown_it/rules_block/reference.py')
-rw-r--r--contrib/python/markdown-it-py/markdown_it/rules_block/reference.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/contrib/python/markdown-it-py/markdown_it/rules_block/reference.py b/contrib/python/markdown-it-py/markdown_it/rules_block/reference.py
new file mode 100644
index 00000000000..ad94d40941e
--- /dev/null
+++ b/contrib/python/markdown-it-py/markdown_it/rules_block/reference.py
@@ -0,0 +1,235 @@
+import logging
+
+from ..common.utils import charCodeAt, isSpace, normalizeReference
+from .state_block import StateBlock
+
+LOGGER = logging.getLogger(__name__)
+
+
+def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool:
+ LOGGER.debug(
+ "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent
+ )
+
+ pos = state.bMarks[startLine] + state.tShift[startLine]
+ maximum = state.eMarks[startLine]
+ nextLine = startLine + 1
+
+ if state.is_code_block(startLine):
+ return False
+
+ if state.src[pos] != "[":
+ return False
+
+ string = state.src[pos : maximum + 1]
+
+ # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip()
+ maximum = len(string)
+
+ labelEnd = None
+ pos = 1
+ while pos < maximum:
+ ch = charCodeAt(string, pos)
+ if ch == 0x5B: # /* [ */
+ return False
+ elif ch == 0x5D: # /* ] */
+ labelEnd = pos
+ break
+ elif ch == 0x0A: # /* \n */
+ if (lineContent := getNextLine(state, nextLine)) is not None:
+ string += lineContent
+ maximum = len(string)
+ nextLine += 1
+ elif ch == 0x5C: # /* \ */
+ pos += 1
+ if (
+ pos < maximum
+ and charCodeAt(string, pos) == 0x0A
+ and (lineContent := getNextLine(state, nextLine)) is not None
+ ):
+ string += lineContent
+ maximum = len(string)
+ nextLine += 1
+ pos += 1
+
+ if (
+ labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A
+ ): # /* : */
+ return False
+
+ # [label]: destination 'title'
+ # ^^^ skip optional whitespace here
+ pos = labelEnd + 2
+ while pos < maximum:
+ ch = charCodeAt(string, pos)
+ if ch == 0x0A:
+ if (lineContent := getNextLine(state, nextLine)) is not None:
+ string += lineContent
+ maximum = len(string)
+ nextLine += 1
+ elif isSpace(ch):
+ pass
+ else:
+ break
+ pos += 1
+
+ # [label]: destination 'title'
+ # ^^^^^^^^^^^ parse this
+ destRes = state.md.helpers.parseLinkDestination(string, pos, maximum)
+ if not destRes.ok:
+ return False
+
+ href = state.md.normalizeLink(destRes.str)
+ if not state.md.validateLink(href):
+ return False
+
+ pos = destRes.pos
+
+ # save cursor state, we could require to rollback later
+ destEndPos = pos
+ destEndLineNo = nextLine
+
+ # [label]: destination 'title'
+ # ^^^ skipping those spaces
+ start = pos
+ while pos < maximum:
+ ch = charCodeAt(string, pos)
+ if ch == 0x0A:
+ if (lineContent := getNextLine(state, nextLine)) is not None:
+ string += lineContent
+ maximum = len(string)
+ nextLine += 1
+ elif isSpace(ch):
+ pass
+ else:
+ break
+ pos += 1
+
+ # [label]: destination 'title'
+ # ^^^^^^^ parse this
+ titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None)
+ while titleRes.can_continue:
+ if (lineContent := getNextLine(state, nextLine)) is None:
+ break
+ string += lineContent
+ pos = maximum
+ maximum = len(string)
+ nextLine += 1
+ titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes)
+
+ if pos < maximum and start != pos and titleRes.ok:
+ title = titleRes.str
+ pos = titleRes.pos
+ else:
+ title = ""
+ pos = destEndPos
+ nextLine = destEndLineNo
+
+ # skip trailing spaces until the rest of the line
+ while pos < maximum:
+ ch = charCodeAt(string, pos)
+ if not isSpace(ch):
+ break
+ pos += 1
+
+ if pos < maximum and charCodeAt(string, pos) != 0x0A and title:
+ # garbage at the end of the line after title,
+ # but it could still be a valid reference if we roll back
+ title = ""
+ pos = destEndPos
+ nextLine = destEndLineNo
+ while pos < maximum:
+ ch = charCodeAt(string, pos)
+ if not isSpace(ch):
+ break
+ pos += 1
+
+ if pos < maximum and charCodeAt(string, pos) != 0x0A:
+ # garbage at the end of the line
+ return False
+
+ label = normalizeReference(string[1:labelEnd])
+ if not label:
+ # CommonMark 0.20 disallows empty labels
+ return False
+
+ # Reference can not terminate anything. This check is for safety only.
+ if silent:
+ return True
+
+ if "references" not in state.env:
+ state.env["references"] = {}
+
+ state.line = nextLine
+
+ # note, this is not part of markdown-it JS, but is useful for renderers
+ if state.md.options.get("inline_definitions", False):
+ token = state.push("definition", "", 0)
+ token.meta = {
+ "id": label,
+ "title": title,
+ "url": href,
+ "label": string[1:labelEnd],
+ }
+ token.map = [startLine, state.line]
+
+ if label not in state.env["references"]:
+ state.env["references"][label] = {
+ "title": title,
+ "href": href,
+ "map": [startLine, state.line],
+ }
+ else:
+ state.env.setdefault("duplicate_refs", []).append(
+ {
+ "title": title,
+ "href": href,
+ "label": label,
+ "map": [startLine, state.line],
+ }
+ )
+
+ return True
+
+
+def getNextLine(state: StateBlock, nextLine: int) -> None | str:
+ endLine = state.lineMax
+
+ if nextLine >= endLine or state.isEmpty(nextLine):
+ # empty line or end of input
+ return None
+
+ isContinuation = False
+
+ # this would be a code block normally, but after paragraph
+ # it's considered a lazy continuation regardless of what's there
+ if state.is_code_block(nextLine):
+ isContinuation = True
+
+ # quirk for blockquotes, this line should already be checked by that rule
+ if state.sCount[nextLine] < 0:
+ isContinuation = True
+
+ if not isContinuation:
+ terminatorRules = state.md.block.ruler.getRules("reference")
+ oldParentType = state.parentType
+ state.parentType = "reference"
+
+ # Some tags can terminate paragraph without empty line.
+ terminate = False
+ for terminatorRule in terminatorRules:
+ if terminatorRule(state, nextLine, endLine, True):
+ terminate = True
+ break
+
+ state.parentType = oldParentType
+
+ if terminate:
+ # terminated by another block
+ return None
+
+ pos = state.bMarks[nextLine] + state.tShift[nextLine]
+ maximum = state.eMarks[nextLine]
+
+ # max + 1 explicitly includes the newline
+ return state.src[pos : maximum + 1]