Complete Implementation
Python
#!/usr/bin/env python3
import shutil, json, argparse
from pathlib import Path
from datetime import datetime
CATEGORIES = {
"Images": [".jpg", ".jpeg", ".png", ".gif", ".webp"],
"Videos": [".mp4", ".mkv", ".avi", ".mov"],
"Audio": [".mp3", ".wav", ".flac", ".aac"],
"Documents": [".pdf", ".doc", ".docx", ".txt", ".md"],
"Code": [".py", ".js", ".ts", ".html", ".css"],
"Archives": [".zip", ".tar", ".gz", ".rar"],
}
def get_category(ext):
for cat, exts in CATEGORIES.items():
if ext.lower() in exts:
return cat
return "Other"
def organize(source, dest, dry_run=False):
moves = []
for file in source.iterdir():
if not file.is_file() or file.name.startswith("."):
continue
cat = get_category(file.suffix)
target_dir = dest / cat
target = target_dir / file.name
if target.exists():
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
target = target_dir / f"{file.stem}_{ts}{file.suffix}"
moves.append({"src": str(file), "dst": str(target), "category": cat})
if not dry_run:
target_dir.mkdir(parents=True, exist_ok=True)
shutil.move(str(file), target)
return moves
parser = argparse.ArgumentParser()
parser.add_argument("source")
parser.add_argument("-d", "--dest")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
src = Path(args.source).resolve()
dst = Path(args.dest).resolve() if args.dest else src
moves = organize(src, dst, args.dry_run)
by_cat = {}
for m in moves:
by_cat.setdefault(m["category"], []).append(m)
for cat, items in sorted(by_cat.items()):
action = "Would move" if args.dry_run else "Moved"
print(f"\n{cat} ({len(items)} files)")
for item in items:
print(f" {action}: {Path(item['src']).name}")
if not args.dry_run and moves:
log = dst / ".organizer_log.json"
log.write_text(json.dumps(moves, indent=2))
print(f"\n{len(moves)} files moved. Log: {log}")Bash
python organizer.py ~/Downloads --dry-run
python organizer.py ~/Downloads -d ~/OrganizedTip: The --dry-run flag is essential for any file manipulation tool. Always let users preview before committing.