plotpx 0.1.7

Pixel-focused plotting engine that renders magnitude grids, heatmaps, and spectra to RGBA buffers
Documentation
#!/usr/bin/env python3
"""Synchronize Julia Project.toml version with Cargo.toml.

This helper lets you bump the crate version once in Cargo.toml and mirror it
into the Julia helper project without manual edits.
"""

from __future__ import annotations

import argparse
import pathlib
import re
import sys
from typing import Iterable, Optional

try:  # pragma: no cover - Python 3.11+
    import tomllib  # type: ignore[attr-defined]
except ModuleNotFoundError:  # pragma: no cover - Python <3.11
    tomllib = None  # type: ignore


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--cargo",
        default="Cargo.toml",
        type=pathlib.Path,
        help="Path to the Cargo.toml file to read (default: Cargo.toml)",
    )
    parser.add_argument(
        "--project",
        default="julia/PlotPx/Project.toml",
        type=pathlib.Path,
        help="Path to the Julia Project.toml to rewrite (default: julia/PlotPx/Project.toml)",
    )
    return parser.parse_args()


def read_cargo_version(path: pathlib.Path) -> str:
    text = path.read_text(encoding="utf-8")
    if tomllib is not None:
        data = tomllib.loads(text)
        try:
            version = data["package"]["version"]
        except KeyError as exc:  # pragma: no cover - defensive guard
            raise SystemExit(f"Cargo.toml missing package.version ({exc})") from exc
        if not isinstance(version, str):  # pragma: no cover - defensive guard
            raise SystemExit("package.version must be a string")
        return version

    match: Optional[re.Match[str]] = re.search(
        r"(?m)^version\s*=\s*\"([^\"]+)\"",
        text,
    )
    if not match:  # pragma: no cover - defensive guard
        raise SystemExit("Unable to locate version in Cargo.toml; install tomli or use Python 3.11+.")
    return match.group(1)


def rewrite_project(path: pathlib.Path, version: str) -> bool:
    if not path.exists():
        raise SystemExit(f"Julia Project.toml not found: {path}")

    lines: Iterable[str] = path.read_text(encoding="utf-8").splitlines(keepends=True)
    updated_lines = []
    saw_version = False

    for line in lines:
        if line.strip().startswith("version ="):
            indent = line.split("version =", 1)[0]
            updated_lines.append(f'{indent}version = "{version}"\n')
            saw_version = True
        else:
            updated_lines.append(line)

    if not saw_version:
        # Insert after the uuid if present, otherwise append near the top.
        inserted = False
        new_lines = []
        for line in updated_lines:
            new_lines.append(line)
            if not inserted and line.strip().startswith("uuid ="):
                new_lines.append(f'version = "{version}"\n')
                inserted = True
        if not inserted:
            new_lines.append(f'version = "{version}"\n')
        updated_lines = new_lines

    new_content = "".join(updated_lines)
    if new_content == path.read_text(encoding="utf-8"):
        return False

    path.write_text(new_content, encoding="utf-8")
    return True


def main() -> int:
    args = parse_args()
    version = read_cargo_version(args.cargo)
    changed = rewrite_project(args.project, version)
    if changed:
        print(f"Updated {args.project} to version {version}")
    else:
        print(f"{args.project} already at version {version}")
    return 0


if __name__ == "__main__":
    sys.exit(main())