69 lines
1.9 KiB
Python
69 lines
1.9 KiB
Python
import re
|
|
from datetime import datetime, timezone
|
|
from typing import Sequence
|
|
|
|
from .models import CaptureRequest
|
|
|
|
|
|
def normalize_tags(tags: Sequence[str]) -> list[str]:
|
|
seen: set[str] = set()
|
|
result: list[str] = []
|
|
for raw in tags:
|
|
tag = raw.strip().lower()
|
|
tag = tag.lstrip("#")
|
|
tag = tag.replace(" ", "_")
|
|
tag = re.sub(r"[^a-z0-9_]", "", tag)
|
|
if tag and tag not in seen:
|
|
seen.add(tag)
|
|
result.append(tag)
|
|
return result
|
|
|
|
|
|
def _org_tags(tags: list[str]) -> str:
|
|
if not tags:
|
|
return ""
|
|
return " :" + ":".join(tags) + ":"
|
|
|
|
|
|
def _created_stamp(created_at: str) -> str:
|
|
"""Parse ISO-8601 datetime and format as org inactive timestamp."""
|
|
try:
|
|
dt = datetime.fromisoformat(created_at)
|
|
except ValueError:
|
|
dt = datetime.now(timezone.utc)
|
|
day_abbr = dt.strftime("%a").lower()
|
|
return f"[{dt.strftime('%Y-%m-%d')} {day_abbr} {dt.strftime('%H:%M')}]"
|
|
|
|
|
|
def _property_drawer(created_stamp: str, source: str, capture_id: str) -> str:
|
|
return (
|
|
":PROPERTIES:\n"
|
|
f":CREATED: {created_stamp}\n"
|
|
f":SOURCE: {source}\n"
|
|
f":ID: {capture_id}\n"
|
|
":END:"
|
|
)
|
|
|
|
|
|
def format_capture(capture: CaptureRequest) -> str:
|
|
tags = normalize_tags(capture.tags)
|
|
tag_str = _org_tags(tags)
|
|
created_stamp = _created_stamp(capture.created_at)
|
|
drawer = _property_drawer(created_stamp, capture.device, capture.id)
|
|
|
|
if capture.kind == "todo":
|
|
heading = f"* TODO {capture.body}{tag_str}"
|
|
return f"{heading}\n{drawer}\n"
|
|
|
|
# note
|
|
lines = capture.body.split("\n")
|
|
first_line = lines[0].rstrip()
|
|
is_multiline = len(lines) > 1 and any(l.strip() for l in lines[1:])
|
|
|
|
if is_multiline:
|
|
heading = f"* note: {first_line}{tag_str}"
|
|
return f"{heading}\n{drawer}\n\n{capture.body}\n"
|
|
else:
|
|
heading = f"* note{tag_str}"
|
|
return f"{heading}\n{drawer}\n\n{capture.body}\n"
|