vault-cli: extract to standalone repo, add flake.nix, move one-off scripts to scripts/one-off/

This commit is contained in:
Hermes
2026-09-16 13:12:01 -04:00
parent 8e8ad466a8
commit 9f3a99a42e
6 changed files with 33 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""Add a short-name alias (ID prefix stripped) to every ID-prefixed note's
frontmatter, so Obsidian's alias-autocomplete can produce clean [[ID|Name]]
links without the user typing the ID. Idempotent: skips notes that already
have that alias or non-ID-prefixed notes.
"""
import os, re, sys
VAULT = "/vault"
# Matches "F.50.12 Jen", "R.31.10 Hindi", "F.22.11 Places We've Slept" etc.
ID_TITLE_RE = re.compile(r'^([RJFI](?:\.\d+){1,3})\s+(.+)$')
FRONTMATTER_RE = re.compile(r'^---\n(.*?)\n---\n', re.DOTALL)
def split_frontmatter(text):
m = FRONTMATTER_RE.match(text)
if m:
return m.group(1), text[m.end():]
return None, text
def add_alias(fm_text, alias):
lines = fm_text.split("\n") if fm_text else []
# find aliases: line (list style) or aliases: [a, b]
alias_line_idx = None
for i, l in enumerate(lines):
if re.match(r'^aliases\s*:', l):
alias_line_idx = i
break
if alias_line_idx is None:
lines.append(f"aliases:\n - {alias}")
return "\n".join(lines)
line = lines[alias_line_idx]
# inline list form: aliases: [a, b]
m = re.match(r'^aliases\s*:\s*\[(.*)\]\s*$', line)
if m:
existing = [x.strip().strip('"\'') for x in m.group(1).split(",") if x.strip()]
if alias in existing:
return None # already present
existing.append(alias)
lines[alias_line_idx] = "aliases: [" + ", ".join(existing) + "]"
return "\n".join(lines)
# block list form: aliases:\n - a\n - b
if line.strip() == "aliases:" or re.match(r'^aliases\s*:\s*$', line):
j = alias_line_idx + 1
existing = []
while j < len(lines) and re.match(r'^\s*-\s*', lines[j]):
existing.append(lines[j].strip()[2:].strip().strip('"\''))
j += 1
if alias in existing:
return None
lines.insert(j, f" - {alias}")
return "\n".join(lines)
# single scalar alias: aliases: Foo
scalar = line.split(":", 1)[1].strip().strip('"\'')
if scalar == alias:
return None
lines[alias_line_idx] = "aliases:\n - " + scalar + f"\n - {alias}"
return "\n".join(lines)
def main():
execute = "--execute" in sys.argv
changed = 0
skipped_no_id = 0
skipped_has_alias = 0
errors = []
for dp, _, fns in os.walk(VAULT):
if ".git" in dp.split(os.sep):
continue
for fn in fns:
if not fn.lower().endswith(".md"):
continue
base = fn[:-3]
m = ID_TITLE_RE.match(base)
if not m:
skipped_no_id += 1
continue
alias = m.group(2)
p = os.path.join(dp, fn)
try:
with open(p, "r", encoding="utf-8", errors="ignore") as f:
text = f.read()
except Exception as e:
errors.append((p, str(e)))
continue
fm, rest = split_frontmatter(text)
new_fm = add_alias(fm or "", alias)
if new_fm is None:
skipped_has_alias += 1
continue
new_text = f"---\n{new_fm}\n---\n\n{rest.lstrip(chr(10))}" if fm is None else f"---\n{new_fm}\n---\n{rest}"
changed += 1
if execute:
with open(p, "w", encoding="utf-8") as f:
f.write(new_text)
print(f"Notes with ID-prefixed title: {changed + skipped_has_alias}")
print(f" Would add alias: {changed}")
print(f" Already had alias: {skipped_has_alias}")
print(f"Notes without ID prefix (skipped, not applicable): {skipped_no_id}")
if errors:
print(f"Errors: {len(errors)}")
for p, e in errors[:10]:
print(f" {p}: {e}")
if not execute:
print("\n(dry run -- pass --execute to write changes)")
if __name__ == "__main__":
main()