"""Render uploaded assets for Markdown documents.""" from __future__ import annotations import json from html import escape from urllib.parse import quote, urlsplit from pathlib import Path from .uploader import UploadedAsset def default_label(asset: UploadedAsset) -> str: return Path(asset.local_path).stem.replace("-", " ").replace("_", " ").strip() or "asset" def render_one(asset: UploadedAsset, *, output: str = "markdown", caption: str | None = None) -> str: label = caption or default_label(asset) if output == "json": return json.dumps(asset.to_dict(), ensure_ascii=False, indent=2) if output == "url": return asset.url if output == "html": return render_html(asset, label) if output == "markdown": return render_markdown(asset, label) raise ValueError(f"Unsupported output format: {output}") def render_many(assets: list[UploadedAsset], *, output: str = "markdown", caption: str | None = None) -> str: if output == "json": return json.dumps([asset.to_dict() for asset in assets], ensure_ascii=False, indent=2) return "\n\n".join(render_one(asset, output=output, caption=caption) for asset in assets) def render_markdown(asset: UploadedAsset, label: str) -> str: safe_label = label.replace('\\', '\\\\').replace('[', '\\[').replace(']', '\\]').replace('\n', ' ') safe_url = quote(valid_url(asset.url), safe=':/?#%&=+@,;~.-_') if asset.kind == "image": return f"![{safe_label}]({safe_url})" if asset.kind in {"audio", "video"}: return render_html(asset, label) return f"[{safe_label}]({safe_url})" def valid_url(url: str) -> str: parsed = urlsplit(url) if parsed.scheme not in {'https', 'http'} or not parsed.netloc or parsed.username or parsed.password: raise ValueError('Expected an HTTP(S) asset URL without embedded credentials.') return url def render_html(asset: UploadedAsset, label: str) -> str: label = escape(label, quote=True) url = escape(valid_url(asset.url), quote=True) if asset.kind == "image": return ( "
\n" f' {label}\n' f"
{label}
\n" "
" ) if asset.kind == "audio": return ( "
\n" f' \n' f"
{label}
\n" "
" ) if asset.kind == "video": return ( "
\n" f' \n' f"
{label}
\n" "
" ) return f'{label}'