from typing import Callable, List, Optional from typing_extensions import override class FormattedError(Exception): pass class TemplateParserError(Exception): def __init__(self, message: str) -> None: self.message = message @override def __str__(self) -> str: return self.message class TokenizationError(Exception): def __init__(self, message: str, line_content: Optional[str] = None) -> None: self.message = message self.line_content = line_content class TokenizerState: def __init__(self) -> None: self.i = 0 self.line = 1 self.col = 1 class Token: def __init__(self, kind: str, s: str, tag: str, line: int, col: int, line_span: int) -> None: self.kind = kind self.s = s self.tag = tag self.line = line self.col = col self.line_span = line_span # These get set during the validation pass. self.start_token: Optional[Token] = None self.end_token: Optional[Token] = None # These get set during the pretty-print phase. self.new_s = "" self.indent: Optional[str] = None self.orig_indent: Optional[str] = None self.child_indent: Optional[str] = None self.indent_is_final = False self.parent_token: Optional[Token] = None def tokenize(text: str, template_format: Optional[str] = None) -> List[Token]: in_code_block = False def advance(n: int) -> None: for _ in range(n): state.i += 1 if state.i >= 0 and text[state.i - 1] == "\n": state.line += 1 state.col = 1 else: state.col += 1 def looking_at(s: str) -> bool: return text[state.i : state.i + len(s)] == s def looking_at_htmlcomment() -> bool: return looking_at("": return text[i:end] if not unclosed_end and text[end] == "<": unclosed_end = end end += 1 raise TokenizationError("Unclosed comment", text[i:unclosed_end]) def get_handlebars_comment(text: str, i: int) -> str: end = i + 5 unclosed_end = 0 while end <= len(text): if text[end - 2 : end] == "}}": return text[i:end] if not unclosed_end and text[end] == "<": unclosed_end = end end += 1 raise TokenizationError("Unclosed comment", text[i:unclosed_end]) def get_template_var(text: str, i: int) -> str: end = i + 3 unclosed_end = 0 while end <= len(text): if text[end - 1] == "}": if end < len(text) and text[end] == "}": end += 1 return text[i:end] if not unclosed_end and text[end] == "<": unclosed_end = end end += 1 raise TokenizationError("Unclosed var", text[i:unclosed_end]) def get_django_comment(text: str, i: int) -> str: end = i + 4 unclosed_end = 0 while end <= len(text): if text[end - 2 : end] == "#}": return text[i:end] if not unclosed_end and text[end] == "<": unclosed_end = end end += 1 raise TokenizationError("Unclosed comment", text[i:unclosed_end]) def get_handlebars_partial(text: str, i: int) -> str: """Works for both partials and partial blocks.""" end = i + 10 unclosed_end = 0 while end <= len(text): if text[end - 2 : end] == "}}": return text[i:end] if not unclosed_end and text[end] == "<": unclosed_end = end end += 1 raise TokenizationError("Unclosed partial", text[i:unclosed_end])