from typing import Callable, List, Optional class FormattedException(Exception): pass class TemplateParserException(Exception): def __init__(self, message: str) -> None: self.message = message def __str__(self) -> str: return self.message class TokenizationException(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 def tokenize(text: str) -> List[Token]: 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 TokenizationException('Unclosed comment', text[i:unclosed_end]) def get_handlebar_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 TokenizationException('Unclosed comment', 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 TokenizationException('Unclosed comment', text[i:unclosed_end]) def get_handlebar_partial(text: str, i: int) -> str: 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 TokenizationException('Unclosed partial', text[i:unclosed_end])