"""A tiny local build backend for paperlib. This keeps `uv run paperlib ...` working without requiring network access to download a third-party build backend. """ from __future__ import annotations import base64 import hashlib from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile PROJECT_ROOT = Path(__file__).resolve().parent SRC_ROOT = PROJECT_ROOT / "src" PACKAGE_ROOT = SRC_ROOT / "paperlib" NAME = "paperlib" VERSION = "0.1.0" def _dist_info_dir() -> str: return f"{NAME}-{VERSION}.dist-info" def _wheel_name() -> str: return f"{NAME}-{VERSION}-py3-none-any.whl" def _metadata_contents() -> bytes: return "\n".join( [ "Metadata-Version: 2.1", f"Name: {NAME}", f"Version: {VERSION}", "Summary: Local-first CLI toolkit for managing a paper library", "", ] ).encode("utf-8") def _wheel_contents() -> bytes: return "\n".join( [ "Wheel-Version: 1.0", "Generator: paperlib_build_backend", "Root-Is-Purelib: true", "Tag: py3-none-any", "", ] ).encode("utf-8") def _entry_points_contents() -> bytes: return b"[console_scripts]\npaperlib = paperlib.cli:main\n" def _hash_bytes(data: bytes) -> tuple[str, str]: digest = hashlib.sha256(data).digest() encoded = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") return f"sha256={encoded}", str(len(data)) def _package_files() -> list[tuple[str, bytes]]: files: list[tuple[str, bytes]] = [] for path in sorted(PACKAGE_ROOT.rglob("*")): if path.is_file(): relative = path.relative_to(SRC_ROOT).as_posix() files.append((relative, path.read_bytes())) return files def _build_records(files: list[tuple[str, bytes]]) -> bytes: record_path = f"{_dist_info_dir()}/RECORD" rows: list[list[str]] = [] for path, data in files: digest, size = _hash_bytes(data) rows.append([path, digest, size]) rows.append([record_path, "", ""]) output: list[str] = [] for row in rows: output.append(",".join(row)) return ("\n".join(output) + "\n").encode("utf-8") def _build_wheel_archive(target: Path) -> str: files = _package_files() files.extend( [ (f"{_dist_info_dir()}/METADATA", _metadata_contents()), (f"{_dist_info_dir()}/WHEEL", _wheel_contents()), (f"{_dist_info_dir()}/entry_points.txt", _entry_points_contents()), ] ) files.append((f"{_dist_info_dir()}/RECORD", _build_records(files))) wheel_path = target / _wheel_name() with ZipFile(wheel_path, "w", compression=ZIP_DEFLATED) as zf: for archive_path, data in files: zf.writestr(archive_path, data) return wheel_path.name def build_wheel( wheel_directory: str, config_settings: dict[str, object] | None = None, metadata_directory: str | None = None, ) -> str: """Build a wheel for paperlib.""" del config_settings, metadata_directory target = Path(wheel_directory) target.mkdir(parents=True, exist_ok=True) return _build_wheel_archive(target) def build_editable( wheel_directory: str, config_settings: dict[str, object] | None = None, metadata_directory: str | None = None, ) -> str: """Build an installable wheel for editable requests. For now, we return a normal pure-Python wheel; that is sufficient for the project's current bootstrap needs. """ return build_wheel(wheel_directory, config_settings, metadata_directory) def get_requires_for_build_wheel( config_settings: dict[str, object] | None = None, ) -> list[str]: """This backend has no external build requirements.""" del config_settings return [] def get_requires_for_build_editable( config_settings: dict[str, object] | None = None, ) -> list[str]: """This backend has no external build requirements.""" del config_settings return [] def prepare_metadata_for_build_wheel( metadata_directory: str, config_settings: dict[str, object] | None = None, ) -> str: """Write minimal dist-info metadata for frontends that request it.""" del config_settings dist_info = Path(metadata_directory) / _dist_info_dir() dist_info.mkdir(parents=True, exist_ok=True) (dist_info / "METADATA").write_bytes(_metadata_contents()) (dist_info / "WHEEL").write_bytes(_wheel_contents()) (dist_info / "entry_points.txt").write_bytes(_entry_points_contents()) (dist_info / "RECORD").write_text("", encoding="utf-8") return dist_info.name def prepare_metadata_for_build_editable( metadata_directory: str, config_settings: dict[str, object] | None = None, ) -> str: """Alias editable metadata generation to the wheel metadata implementation.""" return prepare_metadata_for_build_wheel(metadata_directory, config_settings)