stripped down to skeleton

This commit is contained in:
Manuel Ganter
2026-09-11 09:40:02 +02:00
commit 638cbe6b39
30 changed files with 3384 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""List remaining [[wiki links]] so they can be converted to markdown links by hand.
Legacy `[[wiki links]]` are no longer accepted — `generate-graph.py --validate`
(run by the pre-commit hook) fails on any that remain. This tool locates the
offenders so you can fix them.
Read-only: this script never edits notes. It reports every `[[wiki link]]` still in
the vault (skipping code samples) with its file, line, resolved target, and the full
line as context, plus a suggested markdown conversion. Convert each link yourself,
adding the right `type` and `link-info`, e.g.:
[[resource/old-deadlines]] -> [old deadline table](resource/old-deadlines){"type":"supersedes","link-info":"the table this replaces"}
The relationship `type` must be one of the controlled vocabulary in generate-graph.py
(RELATION_TYPES); the pre-commit hook rejects unknown types.
"""
import argparse
import importlib.util
from collections import defaultdict
from pathlib import Path
from typing import Dict, List
# Reuse the parser/resolver from generate-graph.py (hyphenated name -> load by path).
_GG_PATH = Path(__file__).resolve().with_name("generate-graph.py")
_spec = importlib.util.spec_from_file_location("generate_graph", _GG_PATH)
gg = importlib.util.module_from_spec(_spec)
assert _spec and _spec.loader
_spec.loader.exec_module(gg)
def collect(root: Path):
notes = gg.build_notes(root)
alias_map = gg.build_alias_map(notes)
title_by_id = {n.note_id: n.title for n in notes}
line_cache: Dict[str, List[str]] = {}
rows = []
for n in sorted(notes, key=lambda x: x.rel_path):
wiki = [ref for ref in n.links if ref.kind == "wiki_link"]
if not wiki:
continue
lines = line_cache.setdefault(
n.rel_path, n.path.read_text(encoding="utf-8").splitlines()
)
for ref in wiki:
resolved = alias_map.get(gg.norm(ref.target))
title = title_by_id.get(resolved, "") if resolved else ""
context = lines[ref.lineno - 1].strip() if ref.lineno - 1 < len(lines) else ""
display = title or (resolved or ref.target)
suggestion = f'[{display}]({resolved or ref.target})' + (
'{"type":"related","link-info":"…"}'
)
rows.append(
{
"path": n.rel_path,
"line": ref.lineno,
"raw": ref.raw,
"resolved": resolved or "<unresolved>",
"title": title,
"context": context,
"suggestion": suggestion,
}
)
return rows
def print_grouped(rows) -> None:
by_file: Dict[str, list] = defaultdict(list)
for r in rows:
by_file[r["path"]].append(r)
for path in sorted(by_file):
items = by_file[path]
print(f"\n{path} ({len(items)})")
for r in items:
print(f" L{r['line']}: {r['raw']} -> {r['resolved']}")
if r["context"]:
print(f" context: {r['context']}")
print(f" suggest: {r['suggestion']}")
print("\n" + "=" * 60)
print(f"{len(rows)} wiki link(s) remaining across {len(by_file)} file(s)")
if not rows:
print("All links migrated to markdown form. 🎉")
def print_tsv(rows) -> None:
print("path\tline\traw\tresolved\ttitle\tcontext")
for r in rows:
ctx = r["context"].replace("\t", " ")
print(
f"{r['path']}\t{r['line']}\t{r['raw']}\t{r['resolved']}\t{r['title']}\t{ctx}"
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--format",
choices=["grouped", "tsv"],
default="grouped",
help="output format (default: grouped text)",
)
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
rows = collect(root)
if args.format == "tsv":
print_tsv(rows)
else:
print_grouped(rows)
if __name__ == "__main__":
main()