File size: 3,886 Bytes
12cd919
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env python3
"""Strip machine-specific filesystem paths from anything destined for publication.

The provenance information in these reports is worth publishing; the author's
directory layout is not.  This replaces concrete paths with neutral placeholders
while leaving checkpoint *names* intact, since those are the actual provenance
identifiers and are already public in config.json.

    python scripts/sanitize_paths.py --root . --check
    python scripts/sanitize_paths.py --root . --apply
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

# Ordered: longest / most specific first, so a broad rule cannot eat a narrow one.
REPLACEMENTS: list[tuple[re.Pattern[str], str]] = [
    # Windows form, both single and JSON-escaped backslashes.
    (
        re.compile(r"E:\\{1,2}New folder \(4\)\\{1,2}wraithfast-9b\\{1,2}wraith-finetune"),
        "<workspace>",
    ),
    (re.compile(r"E:\\{1,2}New folder \(4\)"), "<workspace>"),
    # POSIX / WSL form.
    (re.compile(r"/mnt/e/New folder \(4\)/wraithfast-9b/wraith-finetune"), "<workspace>"),
    (re.compile(r"/mnt/e/New folder \(4\)"), "<workspace>"),
    (re.compile(r"/mnt/c/piko9b-weights"), "<local-checkpoint>"),
    # Home directories and Windows user profiles, whoever they belong to.
    (re.compile(r"/home/[A-Za-z0-9_.-]+"), "<home>"),
    (re.compile(r"C:\\{1,2}Users\\{1,2}[A-Za-z0-9_.-]+"), "<home>"),
]

# Leftover bare drive/folder references that survive the rules above.
RESIDUAL = re.compile(r"New folder \(4\)|/mnt/[a-z]/|C:\\Users|/home/[a-z]")

SUFFIXES = {".md", ".json", ".py", ".yaml", ".yml", ".txt", ".cff", ".toml", ".jinja"}
SKIP_DIRS = {".git", "__pycache__", ".ruff_cache", ".pytest_cache", ".venv"}


def iter_files(root: Path):
    for path in sorted(root.rglob("*")):
        if not path.is_file() or path.suffix not in SUFFIXES:
            continue
        if any(part in SKIP_DIRS for part in path.parts):
            continue
        # Never rewrite this script's own rules.
        if path.name == "sanitize_paths.py":
            continue
        yield path


def sanitize(text: str) -> tuple[str, int]:
    total = 0
    for pattern, replacement in REPLACEMENTS:
        text, count = pattern.subn(replacement, text)
        total += count
    return text, total


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--root", type=Path, default=Path("."))
    parser.add_argument("--apply", action="store_true", help="Rewrite files in place.")
    parser.add_argument("--check", action="store_true", help="Exit 1 if anything remains.")
    args = parser.parse_args()

    changed: list[tuple[Path, int]] = []
    residual: list[str] = []

    for path in iter_files(args.root):
        original = path.read_text(encoding="utf-8", errors="replace")
        cleaned, count = sanitize(original)
        if count:
            changed.append((path.relative_to(args.root), count))
            if args.apply:
                path.write_text(cleaned, encoding="utf-8")
        final = cleaned if args.apply or count else original
        for line_number, line in enumerate(final.splitlines(), start=1):
            if RESIDUAL.search(line):
                residual.append(
                    f"{path.relative_to(args.root)}:{line_number}: {line.strip()[:110]}"
                )

    verb = "sanitised" if args.apply else "would sanitise"
    for path, count in changed:
        print(f"  {verb} {path} ({count} occurrence(s))")
    print(f"\n{len(changed)} file(s) {verb}, {sum(c for _, c in changed)} replacement(s)")

    if residual:
        print(f"\n{len(residual)} residual reference(s) needing manual review:")
        for entry in residual[:40]:
            print(f"  {entry}")

    if args.check and residual:
        sys.exit(1)


if __name__ == "__main__":
    main()