ed063ab1fe
Capture desktop-specific system, session, Noctalia, and Pi integration alongside shared laptop support. Retire obsolete X11 configuration and make package ownership explicit for repeatable Decman deployments.
76 lines
2.2 KiB
Python
76 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Safely create one direct child in the configured knowledge vault."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def fail(message: str) -> int:
|
|
print(message, file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
def child_name(raw: str) -> str | None:
|
|
name = raw.strip()
|
|
if not name or name in {".", ".."} or "/" in name or "\\" in name or "\0" in name:
|
|
return None
|
|
if name.startswith("."):
|
|
return None
|
|
return name
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) != 5:
|
|
return fail("usage: create-entry.py VAULT KIND CURRENT_DIRECTORY NAME")
|
|
raw_vault, kind, current_directory, raw_name = sys.argv[1:]
|
|
vault = Path(raw_vault).expanduser().resolve()
|
|
if not vault.is_dir():
|
|
return fail(f"vault does not exist: {vault}")
|
|
if kind not in {"note", "directory"}:
|
|
return fail("kind must be note or directory")
|
|
current = (vault / current_directory).resolve()
|
|
if current != vault and vault not in current.parents:
|
|
return fail("current directory escaped the vault")
|
|
if not current.is_dir():
|
|
return fail("current directory no longer exists")
|
|
|
|
name = child_name(raw_name)
|
|
if name is None:
|
|
return fail("use a non-hidden direct name without slashes")
|
|
if kind == "note" and not name.lower().endswith(".md"):
|
|
name += ".md"
|
|
target = (current / name).resolve()
|
|
if target.parent != current:
|
|
return fail("target escaped the current directory")
|
|
|
|
try:
|
|
if kind == "directory":
|
|
target.mkdir()
|
|
else:
|
|
title = target.stem
|
|
with target.open("x", encoding="utf-8") as handle:
|
|
handle.write(f"# {title}\n")
|
|
except FileExistsError:
|
|
return fail(f"already exists: {name}")
|
|
except OSError as error:
|
|
return fail(f"cannot create {name}: {error}")
|
|
|
|
relative = target.relative_to(vault).as_posix()
|
|
json.dump(
|
|
{
|
|
"kind": kind,
|
|
"relative": relative,
|
|
"name": name,
|
|
"absolute": str(target),
|
|
},
|
|
sys.stdout,
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|