Files
cs249r_book/book/cli/core/artifacts.py
2026-05-30 13:41:34 -04:00

97 lines
3.1 KiB
Python

"""Shared build-artifact cleanup primitives for Binder."""
from __future__ import annotations
import shutil
from dataclasses import dataclass
from pathlib import Path
from rich.console import Console
@dataclass(frozen=True)
class ArtifactCleanupResult:
cleaned_count: int
restored_count: int
def clean_build_artifacts(
book_dir: Path,
*,
dry_run: bool = False,
console: Console | None = None,
) -> ArtifactCleanupResult:
"""Clean Quarto build artifacts and restore fast-build config backups."""
out = console or Console()
book_dir = Path(book_dir)
out.print("[bold blue]Build Artifact Cleanup[/bold blue]")
restored_count = 0
for config_ext in ["_quarto-html.yml", "_quarto-pdf.yml"]:
config_file = book_dir / "config" / config_ext
backup_file = config_file.with_suffix(f"{config_file.suffix}.fast-build-backup")
if backup_file.exists():
if not dry_run:
shutil.copy(backup_file, config_file)
backup_file.unlink()
restored_count += 1
out.print(f"[green] Restored: {config_file.name}[/green]")
else:
out.print(f"[dim] Already clean: {config_file.name}[/dim]")
artifacts_to_clean = [
(book_dir / "_build", "Build directory (all formats)"),
(book_dir / "index_files", "Book index files"),
(book_dir / ".quarto", "Quarto cache (book)"),
]
contents_core = book_dir / "contents" / "core"
if contents_core.exists():
for chapter_dir in contents_core.glob("*/"):
if not chapter_dir.is_dir():
continue
for files_dir in chapter_dir.glob("*_files"):
if not files_dir.is_dir():
continue
figure_html_dir = files_dir / "figure-html"
if figure_html_dir.exists():
artifacts_to_clean.append(
(
figure_html_dir,
f"Quarto figure artifacts ({chapter_dir.name})",
)
)
figure_html_direct = chapter_dir / "figure-html"
if figure_html_direct.exists():
artifacts_to_clean.append(
(
figure_html_direct,
f"Quarto figure artifacts ({chapter_dir.name})",
)
)
cleaned_count = 0
for artifact_path, description in artifacts_to_clean:
if not artifact_path.exists():
continue
out.print(f"[yellow] Removing: {artifact_path.name} ({description})[/yellow]")
if not dry_run:
if artifact_path.is_dir():
shutil.rmtree(artifact_path)
else:
artifact_path.unlink()
cleaned_count += 1
if cleaned_count:
out.print(f"[green] Cleaned {cleaned_count} item(s)[/green]")
else:
out.print("[green] No artifacts to clean[/green]")
return ArtifactCleanupResult(
cleaned_count=cleaned_count,
restored_count=restored_count,
)