from __future__ import annotations from collections.abc import Awaitable, Callable from .render import build_segments # Text types that get the rendered viewer; anything else is served untouched. VIEWABLE_CONTENT_TYPES = {'text/plain', 'text/markdown'} _TRUTHY = {'1', 'true', 'yes'} def _base_content_type(content_type: str) -> str: return (content_type or '').split(';')[0].strip().lower() def wants_text_view(content_type: str, accept_header: str, raw_param: str | None, force_download: bool) -> bool: """Only real browser navigations get HTML; curl, wget and the CLI keep the raw bytes.""" if force_download or (raw_param or '').lower() in _TRUTHY: return False if _base_content_type(content_type) not in VIEWABLE_CONTENT_TYPES: return False # Accept: */* (curl, wget, CLI clients) must not trigger the viewer. return 'text/html' in (accept_header or '').lower() def decode_text(raw: bytes) -> str: """Decode the full document; invalid bytes become replacement characters.""" return raw.decode('utf-8', errors='replace') async def build_view_context( raw: bytes, file_name: str, host: str, root_path: str, raw_url: str, lookup: Callable[[str], Awaitable[dict | None]] | None, ) -> dict: text = decode_text(raw) segments = await build_segments(text, host=host, root_path=root_path, lookup=lookup) return { 'file_name': file_name, 'segments': segments, 'raw_url': raw_url, }