upate handle index script..

This commit is contained in:
ed committed 2026-08-24 15:37:02 -04:00
1 parent 0a281c571c
commit 7e4ed4b303
3 files changed
+150 -6

No files matched your search

+40 -6
View File
@@ -1,11 +1,19 @@
"""Audit every handle index in the vault against real subdirectories.
"""Audit every handle index in the vault against real subdirectories,
and audit the top-level `archive/threads/index.md` against real
handle dirs.
Reports per handle:
For each handle dir:
real — set of subdirectory basenames on disk
links — set of basenames extracted from index.md wikilinks
missing — subdirectories with no wikilink in index.md
stale — wikilinks in index.md whose subdirectory does not exist
For the threads index (top-level archive/threads/index.md):
real — set of handle dir basenames on disk
links — set of handle basenames extracted from wikilinks
missing — handle dirs with no wikilink
stale — wikilinks whose handle dir does not exist
Usage:
python scripts/twitter/audit_handle_index.py # text report
python scripts/twitter/audit_handle_index.py --json # JSON report
@@ -70,6 +78,26 @@ def audit_one(handle_dir: Path) -> dict[str, object]:
}
def audit_threads_index(threads_root: Path) -> dict[str, object]:
"""Audit the top-level archive/threads/index.md against real
handle dirs."""
idx_path = threads_root / "index.md"
real = {
d.name for d in threads_root.iterdir() if d.is_dir() and not d.name.startswith(".")
}
links = _index_links(idx_path)
missing = sorted(real - links)
stale = sorted(links - real)
return {
"handle": "<threads>",
"has_index": idx_path.is_file(),
"real": sorted(real),
"links": sorted(links),
"missing": missing,
"stale": stale,
}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
@@ -92,6 +120,7 @@ def main(argv: list[str] | None = None) -> int:
if not entry.is_dir() or entry.name.startswith("."):
continue
reports.append(audit_one(entry))
reports.append(audit_threads_index(threads_root))
if args.json:
print(json.dumps(reports, indent=2, ensure_ascii=False))
@@ -120,20 +149,25 @@ def main(argv: list[str] | None = None) -> int:
print()
if flagged:
print(f"flagged {flagged} handle(s):")
print(f"flagged {flagged} entry/entries:")
for r in reports:
if not (r["missing"] or r["stale"]):
continue
label = (
"threads index"
if r["handle"] == "<threads>"
else f"{r['handle']} handle index"
)
if r["missing"]:
missing_sample = ", ".join(r["missing"][:5])
if len(r["missing"]) > 5:
missing_sample += f", ... (+{len(r['missing']) - 5} more)"
print(f" {r['handle']}: missing_in_index = {missing_sample}")
print(f" {label}: missing_in_index = {missing_sample}")
if r["stale"]:
stale_sample = ", ".join(r["stale"])
print(f" {r['handle']}: stale_in_index = {stale_sample}")
print(f" {label}: stale_in_index = {stale_sample}")
else:
print("clean: no handle has missing or stale wikilinks")
print("clean: no handle or threads index has missing or stale wikilinks")
return 0
@@ -0,0 +1,107 @@
from __future__ import annotations
import json
import shutil
import tempfile
import unittest
from pathlib import Path
from twitter.audit_handle_index import (
audit_one,
audit_threads_index,
main,
)
def _write_handle(
handle_dir: Path,
wikilinks: list[str] | None,
subdirs: list[str] | None,
) -> None:
handle_dir.mkdir(parents=True, exist_ok=True)
for name in subdirs or []:
(handle_dir / name).mkdir()
if wikilinks is not None:
body = "\n".join(f"- [[archive/threads/{handle_dir.name}/{w}]]" for w in wikilinks)
(handle_dir / "index.md").write_text(
"---\ntitle: x\n---\n\n" + body + "\n",
encoding="utf-8",
newline="\n",
)
def _write_threads_index(threads_root: Path, handles: list[str]) -> None:
body = "\n".join(f"- [[archive/threads/{h}]]" for h in handles)
(threads_root / "index.md").write_text(
"---\ntitle: Threads\n---\n\n" + body + "\n",
encoding="utf-8",
newline="\n",
)
class ThreadsIndexAuditTests(unittest.TestCase):
def setUp(self) -> None:
self.tmp = Path(tempfile.mkdtemp(prefix="threads_audit_"))
self.threads_root = self.tmp / "archive" / "threads"
self.threads_root.mkdir(parents=True)
def tearDown(self) -> None:
shutil.rmtree(self.tmp, ignore_errors=True)
def test_threads_index_clean(self) -> None:
_write_handle(self.threads_root / "alice", None, ["2024-01-01"])
_write_handle(self.threads_root / "bob", None, ["2024-02-02"])
_write_threads_index(self.threads_root, ["alice", "bob"])
result = audit_threads_index(self.threads_root)
self.assertEqual(result["missing"], [])
self.assertEqual(result["stale"], [])
self.assertEqual(set(result["real"]), {"alice", "bob"})
def test_threads_index_missing_handle(self) -> None:
# bob exists on disk but is not wikilinked in threads index
_write_handle(self.threads_root / "alice", None, [])
_write_handle(self.threads_root / "bob", None, [])
_write_threads_index(self.threads_root, ["alice"])
result = audit_threads_index(self.threads_root)
self.assertEqual(result["missing"], ["bob"])
self.assertEqual(result["stale"], [])
def test_threads_index_stale_wikilink(self) -> None:
# ghost is wikilinked but no handle dir exists
_write_handle(self.threads_root / "alice", None, [])
_write_threads_index(self.threads_root, ["alice", "ghost"])
result = audit_threads_index(self.threads_root)
self.assertEqual(result["missing"], [])
self.assertEqual(result["stale"], ["ghost"])
class AuditorCliTests(unittest.TestCase):
def setUp(self) -> None:
self.tmp = Path(tempfile.mkdtemp(prefix="threads_audit_cli_"))
self.threads_root = self.tmp / "archive" / "threads"
self.threads_root.mkdir(parents=True)
def tearDown(self) -> None:
shutil.rmtree(self.tmp, ignore_errors=True)
def test_main_text_includes_threads_index_entry(self) -> None:
_write_handle(self.threads_root / "alice", None, [])
_write_handle(self.threads_root / "bob", None, [])
_write_threads_index(self.threads_root, ["alice"])
# bob is missing from the threads index; main() should flag it
import io
from contextlib import redirect_stdout
buf = io.StringIO()
with redirect_stdout(buf):
rc = main(["--vault", str(self.tmp)])
self.assertEqual(rc, 0)
out = buf.getvalue()
self.assertIn("alice", out)
self.assertIn("bob", out)
self.assertIn("threads index", out)
self.assertIn("missing_in_index", out)
if __name__ == "__main__":
unittest.main()