#!/usr/bin/env python3 """Apply the two link-fix passes across /vault: 1. fix_map: [[OldTarget]] -> [[NewNote|OldTarget]] (preserves display text) Also handles piped/heading variants: [[OldTarget|Display]] -> [[NewNote|Display]] [[OldTarget#Heading]] -> [[NewNote#Heading]] 2. unlink_targets: [[Target]] -> Target (plain text, drop brackets) Also handles piped: [[Target|Display]] -> Display """ import os, re, json, sys VAULT = "/vault" with open("/tmp/link_fix_map.json") as f: FIX_MAP = json.load(f) with open("/tmp/unlink_targets.json") as f: UNLINK = set(json.load(f)) # Full link regex capturing target, optional #heading, optional |display LINK_RE = re.compile(r'\[\[([^\]|#]+)(#[^\]|]*)?(\|[^\]]*)?\]\]') def process_text(text): changed = False def repl(m): nonlocal changed target = m.group(1) heading = m.group(2) or "" pipe_display = m.group(3) # includes leading | target_stripped = target.strip() if target_stripped in FIX_MAP: new_target = FIX_MAP[target_stripped] changed = True if pipe_display: # keep existing custom display text return f"[[{new_target}{heading}{pipe_display}]]" else: # preserve original text as display so prose doesn't change visually return f"[[{new_target}{heading}|{target_stripped}]]" if target_stripped in UNLINK: changed = True if pipe_display: display = pipe_display[1:] # strip leading | return display else: return target_stripped return m.group(0) # unchanged new_text = LINK_RE.sub(repl, text) return new_text, changed def main(): execute = "--execute" in sys.argv files_changed = 0 total_fix_applied = 0 total_unlink_applied = 0 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 p = os.path.join(dp, fn) try: with open(p, "r", encoding="utf-8", errors="ignore") as f: text = f.read() except Exception: continue new_text, changed = process_text(text) if changed: files_changed += 1 if execute: with open(p, "w", encoding="utf-8") as f: f.write(new_text) print(f"Files changed: {files_changed}") if not execute: print("(dry run -- pass --execute to write)") if __name__ == "__main__": main()