71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
"""Journal entry body as Markdown. Media stays in media_assets; the body only references it.
|
|
|
|
Canonical embed: 
|
|
Legacy [[media:<uuid>|caption]] is still read and normalised on save.
|
|
|
|
Tokens never go to the Privacy Gateway; captions may, as user wording.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
UUID = r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
|
UUID_RE = re.compile(rf"^{UUID}$")
|
|
MD_MEDIA_RE = re.compile(rf"!\[([^\]]*)\]\(kansho-media:({UUID})\)")
|
|
LEGACY_MEDIA_RE = re.compile(rf"\[\[media:({UUID})(?:\|([^\]]*))?\]\]")
|
|
HEADING_PREFIX = re.compile(r"^#{1,6}\s+")
|
|
|
|
|
|
def clean_title(raw: str) -> str:
|
|
"""First line only, without Markdown heading marks."""
|
|
line = (raw or "").strip().splitlines()[0].strip() if raw else ""
|
|
return HEADING_PREFIX.sub("", line).strip()
|
|
|
|
|
|
def media_token(media_id: str, caption: str = "") -> str:
|
|
cap = (caption or "").replace("]", "").replace("\n", " ").strip()
|
|
return f""
|
|
|
|
|
|
def media_ids(body: str) -> list[str]:
|
|
seen: list[str] = []
|
|
for match in MD_MEDIA_RE.finditer(body or ""):
|
|
media_id = match.group(2)
|
|
if media_id not in seen:
|
|
seen.append(media_id)
|
|
for match in LEGACY_MEDIA_RE.finditer(body or ""):
|
|
media_id = match.group(1)
|
|
if media_id not in seen:
|
|
seen.append(media_id)
|
|
return seen
|
|
|
|
|
|
def to_markdown(body: str) -> str:
|
|
"""Legacy media tokens become Markdown image syntax. Already-Markdown is unchanged."""
|
|
|
|
def repl(match: re.Match) -> str:
|
|
return media_token(match.group(1), match.group(2) or "")
|
|
|
|
return LEGACY_MEDIA_RE.sub(repl, body or "")
|
|
|
|
|
|
def plain_text(body: str) -> str:
|
|
"""User wording only: no media URLs, no asset IDs, no Markdown markup. Captions stay."""
|
|
text = to_markdown(body)
|
|
|
|
def caption(match: re.Match) -> str:
|
|
return (match.group(1) or "").strip()
|
|
|
|
text = MD_MEDIA_RE.sub(caption, text)
|
|
text = re.sub(r"<u>(.*?)</u>", r"\1", text, flags=re.I | re.S)
|
|
text = re.sub(r"^#{1,6}\s+", "", text, flags=re.M)
|
|
text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
|
|
text = re.sub(r"__(.+?)__", r"\1", text)
|
|
text = re.sub(r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", r"\1", text)
|
|
text = re.sub(r"^\s*[-*+]\s+", "", text, flags=re.M)
|
|
text = re.sub(r"^\s*\d+\.\s+", "", text, flags=re.M)
|
|
text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text)
|
|
text = re.sub(r"`([^`]+)`", r"\1", text)
|
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
return text.strip()
|