NicholaiVogel 3c83039a71 Add 3DE to Nuke Track Converter v2.0
- Complete rewrite with beautiful Rich TUI interface
- Interactive and CLI modes for flexibility
- Robust error handling with clear, helpful messages
- Gap filling with linear interpolation support
- Coordinate system transforms (pixels/normalized)
- Auto-generated output filenames from input
- Configurable resolution and Nuke versions
- Batch processing support via CLI
- Comprehensive documentation in Scripts/README_CONVERTER.md
- Updated main README.md with Scripts section
2025-10-07 21:14:33 -06:00

236 lines
6.8 KiB
Python

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]