from __future__ import annotations import re # Only http(s) is linkified; everything else stays inert plain text. _URL_PATTERN = re.compile(r'https?://[^\s<>"\'`]+', re.IGNORECASE) # Prose punctuation that is almost never part of the URL itself. _TRAILING_PUNCTUATION = '.,;:!?\'"' _BRACKET_PAIRS = {')': '(', ']': '[', '}': '{'} def _trim_trailing(url: str) -> str: """Give punctuation that belongs to the surrounding sentence back to the text.""" while url: last = url[-1] if last in _TRAILING_PUNCTUATION: url = url[:-1] continue opening = _BRACKET_PAIRS.get(last) if opening is not None and url.count(opening) < url.count(last): url = url[:-1] continue break return url def tokenize(text: str) -> list[tuple[str, str]]: """Split text into ('text', value) and ('url', value) tokens, order preserved.""" tokens: list[tuple[str, str]] = [] cursor = 0 for match in _URL_PATTERN.finditer(text): url = _trim_trailing(match.group(0)) if not url: continue start = match.start() end = start + len(url) if start > cursor: tokens.append(('text', text[cursor:start])) tokens.append(('url', url)) cursor = end if cursor < len(text): tokens.append(('text', text[cursor:])) return tokens