Merge branch 'master' of C:\projects\manual_slop into tier2/post_module_taxonomy_de_cruft_20260627
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"""Organize docs/reports/ files into week folders named <YYYY>-<MM>-<DD>.
|
||||
|
||||
Scheme: ShareX-style <YYYY>-<MM>-<W> where <W> resolves to the day-of-month
|
||||
(zero-padded) of the week's Monday. Example: files from the week of Mon
|
||||
June 22, 2026 land in docs/reports/2026-06-22/.
|
||||
|
||||
Only moves files from OLD weeks; the current week's files stay in place
|
||||
so in-flight work isn't buried.
|
||||
|
||||
Date resolution per file:
|
||||
1. Parse an 8-digit YYYYMMDD or YYYY-MM-DD substring from the filename
|
||||
(most reports embed one, e.g. TRACK_COMPLETION_..._20260627.md).
|
||||
2. Fall back to the file's mtime. NOTE: git checkout resets mtime, so
|
||||
undated files may be mis-classified as 'current week' and left in place.
|
||||
|
||||
Idempotent: subdirectories (existing week folders) are skipped; files
|
||||
already at their computed destination are skipped.
|
||||
|
||||
Non-recursive: only immediate children of the reports dir are scanned.
|
||||
Subdirectories (existing week folders, category folders like
|
||||
code_path_audit/ + license_cve_audit/) are never entered; their contents
|
||||
are left untouched. Only loose .md (or other) files in the immediate
|
||||
reports dir are candidates for moving.
|
||||
|
||||
Usage:
|
||||
python scripts/organize_reports.py # dry-run; print plan
|
||||
python scripts/organize_reports.py --apply # actually move files
|
||||
python scripts/organize_reports.py --json # dry-run; JSON output
|
||||
python scripts/organize_reports.py --dir docs/reports
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
_DATE_REGEX = re.compile(r"(\d{4})[-_]?(\d{2})[-_]?(\d{2})")
|
||||
|
||||
|
||||
def _week_monday(d: date) -> date:
|
||||
return d - timedelta(days=d.weekday())
|
||||
|
||||
|
||||
def _parse_filename_date(name: str) -> date | None:
|
||||
m = _DATE_REGEX.search(name)
|
||||
if not m:
|
||||
return None
|
||||
y, mo, dy = int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||
if not (2000 <= y < 2100 and 1 <= mo <= 12 and 1 <= dy <= 31):
|
||||
return None
|
||||
try:
|
||||
return date(y, mo, dy)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _file_week_monday(path: Path) -> date | None:
|
||||
fd = _parse_filename_date(path.name)
|
||||
if fd is not None:
|
||||
return _week_monday(fd)
|
||||
try:
|
||||
mt = path.stat().st_mtime
|
||||
except OSError:
|
||||
return None
|
||||
return _week_monday(date.fromtimestamp(mt))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Organize docs/reports/ files into week folders named <YYYY>-<MM>-<DD> (Monday of the file's week). Old weeks only; current week's files stay in place."
|
||||
)
|
||||
parser.add_argument("--apply", action="store_true", help="Actually move files (default: dry-run)")
|
||||
parser.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable output")
|
||||
parser.add_argument("--dir", default="docs/reports", help="Reports directory (default: docs/reports)")
|
||||
args = parser.parse_args()
|
||||
|
||||
reports = Path(args.dir)
|
||||
if not reports.is_dir():
|
||||
print(f"error: {reports} is not a directory", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
subdirs_skipped = [e.name for e in sorted(reports.iterdir(), key=lambda e: e.name) if e.is_dir()]
|
||||
current_monday = _week_monday(date.today())
|
||||
moves: list[dict] = []
|
||||
skipped_current_week: list[str] = []
|
||||
skipped_no_date: list[str] = []
|
||||
skipped_already_in_place: list[str] = []
|
||||
|
||||
for entry in sorted(reports.iterdir(), key=lambda e: e.name):
|
||||
if entry.is_dir():
|
||||
continue
|
||||
if not entry.is_file():
|
||||
continue
|
||||
fm = _file_week_monday(entry)
|
||||
if fm is None:
|
||||
skipped_no_date.append(entry.name)
|
||||
continue
|
||||
if fm >= current_monday:
|
||||
skipped_current_week.append(entry.name)
|
||||
continue
|
||||
dest_dir = reports / fm.isoformat()
|
||||
dest = dest_dir / entry.name
|
||||
if dest.exists():
|
||||
skipped_already_in_place.append(entry.name)
|
||||
continue
|
||||
moves.append({"src": str(entry), "dest": str(dest), "week": fm.isoformat()})
|
||||
|
||||
if args.json:
|
||||
print(json.dumps({
|
||||
"current_week_monday": current_monday.isoformat(),
|
||||
"reports_dir": str(reports),
|
||||
"moves": moves,
|
||||
"skipped_current_week": skipped_current_week,
|
||||
"skipped_no_date": skipped_no_date,
|
||||
"skipped_already_in_place": skipped_already_in_place,
|
||||
"subdirs_skipped": subdirs_skipped,
|
||||
}, indent=2))
|
||||
return 0
|
||||
|
||||
print(f"Current week Monday: {current_monday.isoformat()} (files from this week stay put)")
|
||||
print(f"Reports dir: {reports}")
|
||||
print()
|
||||
if not moves:
|
||||
print("Nothing to move.")
|
||||
else:
|
||||
tag = "MOVE" if args.apply else "DRY "
|
||||
print(f"{tag}: {len(moves)} file(s)")
|
||||
for mv in moves:
|
||||
print(f" {tag} {mv['src']} -> {mv['dest']}")
|
||||
|
||||
if skipped_current_week:
|
||||
print(f"\nSkipped (current week): {len(skipped_current_week)}")
|
||||
for n in skipped_current_week:
|
||||
print(f" - {n}")
|
||||
if skipped_already_in_place:
|
||||
print(f"\nSkipped (already in destination): {len(skipped_already_in_place)}")
|
||||
for n in skipped_already_in_place:
|
||||
print(f" - {n}")
|
||||
if skipped_no_date:
|
||||
print(f"\nSkipped (no parseable date): {len(skipped_no_date)}")
|
||||
for n in skipped_no_date:
|
||||
print(f" - {n}")
|
||||
if subdirs_skipped:
|
||||
print(f"\nSubdirectories skipped (non-recursive; left untouched): {len(subdirs_skipped)}")
|
||||
for n in subdirs_skipped:
|
||||
print(f" - {n}")
|
||||
|
||||
if args.apply and moves:
|
||||
for mv in moves:
|
||||
dest_path = Path(mv["dest"])
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(mv["src"], mv["dest"])
|
||||
print(f"\nApplied: moved {len(moves)} file(s).")
|
||||
elif moves:
|
||||
print("\nDry run. Pass --apply to move.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user