51 lines
1.5 KiB
Python
51 lines
1.5 KiB
Python
"""Vault file IO shared by the markdown frontend and its mirror."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import os
|
|
import tempfile
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import aiofile
|
|
import frontmatter
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
__all__ = ["read_or_empty", "reattach_frontmatter", "write_atomic"]
|
|
|
|
|
|
async def read_or_empty(path: Path) -> str:
|
|
if not path.exists(): # noqa: ASYNC240
|
|
return ""
|
|
async with aiofile.async_open(path, "r", encoding="utf-8") as f:
|
|
return await f.read()
|
|
|
|
|
|
async def write_atomic(path: Path, content: str) -> None:
|
|
"""Write via tmp + ``os.replace`` in the same directory: no torn reads."""
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
fd, tmp_path = tempfile.mkstemp(
|
|
prefix=f".{path.name}.", suffix=".tmp", dir=str(path.parent)
|
|
)
|
|
try:
|
|
async with aiofile.async_open(tmp_path, "w", encoding="utf-8") as f:
|
|
await f.write(content)
|
|
os.close(fd)
|
|
os.replace(tmp_path, path) # noqa: PTH105
|
|
except BaseException:
|
|
with contextlib.suppress(OSError):
|
|
os.close(fd)
|
|
with contextlib.suppress(OSError):
|
|
os.unlink(tmp_path) # noqa: PTH108
|
|
raise
|
|
|
|
|
|
def reattach_frontmatter(metadata: dict[str, Any], body: str) -> str:
|
|
"""Re-emit a ``.md`` with YAML frontmatter; no block at all for empty metadata."""
|
|
if not metadata:
|
|
return body if body.endswith("\n") else body + "\n"
|
|
post = frontmatter.Post(content=body, **metadata)
|
|
return frontmatter.dumps(post) + "\n"
|