#!/usr/bin/env python3 """ task_utils.py — cross-platform file operations for the Taskfile pipeline. Replaces shell-specific commands (PowerShell Compress-Archive, Remove-Item, etc.) with plain Python stdlib calls that behave identically on Windows, Linux, and macOS. Called from Task as: python ../resources/scripts/task_utils.py [args...] Subcommands: zip SRC_DIR DEST_ZIP Zip the contents of SRC_DIR into DEST_ZIP (overwrites if it exists). Fails with a clear message (exit 1) if SRC_DIR doesn't exist. zip-if-exists SRC_DIR DEST_ZIP Same as `zip`, but exits 0 with a warning (no error) if SRC_DIR doesn't exist, instead of failing. Used for optional things like a project's data/ folder. clean-project PROJECT_DIR Remove _output/, .quarto/, and any *_files/*_cache directories found anywhere under PROJECT_DIR. Safe to call even if nothing exists yet. copy-if-exists SRC DEST Copy a single file from SRC to DEST if SRC exists; otherwise print a warning and exit 0 (does not fail the pipeline). today Print today's date as YYYY-MM-DD (used for the finalized/ folder name). No platform-specific date command needed. """ import shutil import sys import zipfile from datetime import date from pathlib import Path def cmd_zip(src_dir: str, dest_zip: str, allow_missing: bool) -> int: src = Path(src_dir) dest = Path(dest_zip) if not src.exists() or not src.is_dir(): msg = f"'{src}' does not exist or is not a directory" if allow_missing: print(f"WARNING: {msg} — skipping zip of {dest}") return 0 print(f"ERROR: {msg} — did you render first?", file=sys.stderr) return 1 files = [p for p in src.rglob("*") if p.is_file()] if not files: msg = f"'{src}' exists but contains no files" if allow_missing: print(f"WARNING: {msg} — skipping zip of {dest}") return 0 print(f"ERROR: {msg}", file=sys.stderr) return 1 dest.parent.mkdir(parents=True, exist_ok=True) if dest.exists(): dest.unlink() with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf: for f in files: zf.write(f, f.relative_to(src)) print(f"Created {dest} ({len(files)} files)") return 0 def cmd_clean_project(project_dir: str) -> int: root = Path(project_dir) for name in ("_output", ".quarto"): target = root / name if target.exists(): shutil.rmtree(target, ignore_errors=True) print(f"Removed {target}") for pattern in ("*_files", "*_cache"): for match in root.rglob(pattern): if match.is_dir(): shutil.rmtree(match, ignore_errors=True) print(f"Removed {match}") return 0 def cmd_clean_zips(project_dir: str) -> int: root = Path(project_dir) for match in root.rglob("*.zip"): if match.is_file(): shutil.rmtree(match, ignore_errors=True) print(f"Removed {match}") return 0 def cmd_copy_if_exists(src: str, dest: str) -> int: src_path = Path(src) dest_path = Path(dest) if not src_path.exists(): print(f"WARNING: '{src_path}' not found, skipping copy") return 0 dest_path.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src_path, dest_path) print(f"Copied {src_path} -> {dest_path}") return 0 def cmd_today() -> int: print(date.today().isoformat()) return 0 def main() -> int: if len(sys.argv) < 2: print(__doc__, file=sys.stderr) return 1 subcommand = sys.argv[1] args = sys.argv[2:] if subcommand == "zip": if len(args) != 2: print("usage: task_utils.py zip SRC_DIR DEST_ZIP", file=sys.stderr) return 1 return cmd_zip(args[0], args[1], allow_missing=False) if subcommand == "zip-if-exists": if len(args) != 2: print("usage: task_utils.py zip-if-exists SRC_DIR DEST_ZIP", file=sys.stderr) return 1 return cmd_zip(args[0], args[1], allow_missing=True) if subcommand == "clean-project": if len(args) != 1: print("usage: task_utils.py clean-project PROJECT_DIR", file=sys.stderr) return 1 return cmd_clean_project(args[0]) if subcommand == "clean-zips": if len(args) != 1: print("usage: task_utils.py clean-zips PROJECT_DIR", file=sys.stderr) return 1 return cmd_clean_zips(args[0]) if subcommand == "copy-if-exists": if len(args) != 2: print("usage: task_utils.py copy-if-exists SRC DEST", file=sys.stderr) return 1 return cmd_copy_if_exists(args[0], args[1]) if subcommand == "today": return cmd_today() print(f"ERROR: unknown subcommand '{subcommand}'", file=sys.stderr) print(__doc__, file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main())