From be7a7e614bb8168e3252ae29947dfca8f0a8834b Mon Sep 17 00:00:00 2001 From: ruben-iteng <94007802+ruben-iteng@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:02:45 -0700 Subject: [PATCH] Replace outdated scripts with `ato package` cli --- README.md | 16 +- scripts/build-all.sh | 59 ------ scripts/build_all.py | 131 -------------- scripts/bump_versions.py | 111 ------------ scripts/check_status.py | 260 --------------------------- scripts/discover-changed-packages.sh | 83 --------- scripts/package_info.py | 232 ------------------------ scripts/review_modified_pcbs.py | 204 --------------------- 8 files changed, 9 insertions(+), 1087 deletions(-) delete mode 100755 scripts/build-all.sh delete mode 100644 scripts/build_all.py delete mode 100644 scripts/bump_versions.py delete mode 100644 scripts/check_status.py delete mode 100755 scripts/discover-changed-packages.sh delete mode 100644 scripts/package_info.py delete mode 100755 scripts/review_modified_pcbs.py diff --git a/README.md b/README.md index 741521772..07d4ccbfd 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,16 @@ Please ensure your PR: ### Building Packages -To build all packages in the repository, use the `build-all.sh` script: +Batch operations are provided by the `ato` CLI itself — point it at the +`packages/` directory: ```bash -# Build all packages -./build-all.sh +# Build every package +ato build packages/ -# Build all packages with specific flags (e.g., --frozen) -./build-all.sh --frozen -``` +# List packages with identifier/version +ato package list packages/ -The script will attempt to build each package and provide a summary report of successes and failures. +# Show latest build status per target +ato package status packages/ +``` diff --git a/scripts/build-all.sh b/scripts/build-all.sh deleted file mode 100755 index 33d0a3ac2..000000000 --- a/scripts/build-all.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash - -# Store the original directory -ORIGINAL_DIR=$(pwd) - -# Arrays to store results -declare -a successful_builds=() -declare -a failed_builds=() - -# Function to handle errors -handle_error() { - echo "āŒ Error occurred in directory: $1" - failed_builds+=("$1") - cd "$ORIGINAL_DIR/packages" - return 1 -} - -# Change to packages directory -cd packages - -# Loop through all directories -for dir in */; do - if [ -d "$dir" ]; then - echo "šŸ”Ø Building package in $dir with arguments: $@" - if (cd "$dir" && ato build "$@"); then - echo "āœ… Successfully built $dir" - successful_builds+=("$dir") - else - handle_error "$dir" - # Continue to next directory even if this one failed - continue - fi - fi -done - -# Return to original directory -cd "$ORIGINAL_DIR" - -# Print summary report -echo -e "\nšŸ“Š Build Summary Report" -echo "=====================" -echo -e "\nāœ… Successful builds (${#successful_builds[@]}):" -for build in "${successful_builds[@]}"; do - echo " - $build" -done - -echo -e "\nāŒ Failed builds (${#failed_builds[@]}):" -for build in "${failed_builds[@]}"; do - echo " - $build" -done - -echo -e "\nšŸ“ˆ Total packages: $((${#successful_builds[@]} + ${#failed_builds[@]}))" -echo "āœ… Successful: ${#successful_builds[@]}" -echo "āŒ Failed: ${#failed_builds[@]}" - -# Exit with error if any builds failed -if [ ${#failed_builds[@]} -gt 0 ]; then - exit 1 -fi diff --git a/scripts/build_all.py b/scripts/build_all.py deleted file mode 100644 index 054bd5c43..000000000 --- a/scripts/build_all.py +++ /dev/null @@ -1,131 +0,0 @@ -#! uv run -# /// script -# dependencies = [ -# "typer>=0.12", -# "typing_extensions>=4.10.0", -# "rich>=13.0.0", -# ] -# /// - -import re -import typer -import subprocess -from concurrent.futures import ProcessPoolExecutor, as_completed -from pathlib import Path -from rich.console import Console -from rich.table import Table -from rich.progress import ( - Progress, - SpinnerColumn, - BarColumn, - TextColumn, - TimeElapsedColumn, -) - -app = typer.Typer() - -console = Console() - - -def build_package(package_dir: Path, args: tuple) -> tuple[str, bool]: - """Builds a single package using 'ato build' and captures output.""" - package_name = package_dir.name - args_str = " with args:" + (" ".join(args)) if args else "" - console.print(f"[yellow]šŸ”Ø Building package in {package_name}{args_str}[/yellow]") - try: - # Convert args tuple to a list for subprocess - process = subprocess.run( - ["ato", "build"] + list(args), - cwd=package_dir, - capture_output=True, - text=True, - check=True, - ) - console.print(f"[green]āœ… Successfully built {package_name}[/green]") - # console.print(f"Output:\n{process.stdout}") # Optional: print stdout - return package_name, True - except subprocess.CalledProcessError as e: - console.print(f"[red]āŒ Error occurred in directory: {package_name}[/red]") - # console.print(f"Stderr:\n{e.stderr}") - return package_name, False - - -@app.command() -def main( - args: list[str] = typer.Argument(None, help="Arguments to pass to ato build"), - package_regex: str = typer.Option(None, help="Regex to filter packages to build"), -): - """Builds all packages in the 'packages' directory in parallel.""" - original_dir = Path.cwd() - packages_dir = Path("packages") - - if not packages_dir.is_dir(): - console.print( - f"[red]āŒ Error: 'packages' directory not found in {original_dir}[/red]" - ) - return - - package_subdirs = [d for d in packages_dir.iterdir() if d.is_dir()] - - if not package_subdirs: - console.print(f"[yellow]No packages found in {packages_dir}[/yellow]") - return - - if package_regex: - package_subdirs = [ - d for d in package_subdirs if re.match(package_regex, d.name) - ] - - build_args = tuple(args) if args else () - - successful_builds = [] - failed_builds = [] - - with Progress( - SpinnerColumn(), - TextColumn("[progress.description]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - TimeElapsedColumn(), - console=console, - ) as progress: - task = progress.add_task( - "[cyan]Building packages...[/cyan]", total=len(package_subdirs) - ) - with ProcessPoolExecutor() as executor: - futures = { - executor.submit(build_package, subdir, build_args): subdir - for subdir in package_subdirs - } - for future in as_completed(futures): - package_name, success = future.result() - if success: - successful_builds.append(package_name) - else: - failed_builds.append(package_name) - progress.update(task, advance=1) - - # Print summary report - console.print("\n[bold blue]šŸ“Š Build Summary Report[/bold blue]") - table = Table(show_header=True, header_style="bold magenta") - table.add_column("Status", style="dim", width=12) - table.add_column("Package Name") - - console.print(f"\n[green]āœ… Successful builds ({len(successful_builds)}):[/green]") - for build in sorted(successful_builds): - console.print(f" - {build}") - - console.print(f"\n[red]āŒ Failed builds ({len(failed_builds)}):[/red]") - for build in sorted(failed_builds): - console.print(f" - {build}") - - console.print(f"\n[bold]šŸ“ˆ Total packages: {len(package_subdirs)}[/bold]") - console.print(f"[green]āœ… Successful: {len(successful_builds)}[/green]") - console.print(f"[red]āŒ Failed: {len(failed_builds)}[/red]") - - if failed_builds: - raise typer.Exit(code=1) - - -if __name__ == "__main__": - app() diff --git a/scripts/bump_versions.py b/scripts/bump_versions.py deleted file mode 100644 index 8377a8bcd..000000000 --- a/scripts/bump_versions.py +++ /dev/null @@ -1,111 +0,0 @@ -# /// script -# dependencies = [ -# "typer>=0.12", -# "pyyaml>=6.0.2", -# "typing_extensions>=4.10.0", -# ] -# /// - - -# INSTRUCTIONS: -# - this shall be run with uv with embedded depencies so we need no venv etc -# - use typer for the cli -# - argumment: major, minor, patch (which one to bump) -# - optional regex for the package name -# - look for all ato.yaml files -# - use yaml to read the version, and bump the appropriate field -# - save yaml again (without changing formatting or anything else in the file) -# - (there is a library for that kind of yaml operation: ruamel.yaml) - -import typer -from typing_extensions import Annotated -from pathlib import Path -import re -import yaml - -app = typer.Typer() - - -@app.command() -def main( - version_part: Annotated[ - str, - typer.Argument( - help="Which part of the version to bump: major, minor, or patch." - ), - ], - package_regex: Annotated[ - str, typer.Option(help="Optional regex to filter package names.") - ] = ".*", - dry_run: Annotated[ - bool, typer.Option(help="Dry run, don't actually change anything.") - ] = False, -): - """ - Bumps the version in ato.yaml files. - """ - if version_part not in ["major", "minor", "patch"]: - print( - f"Error: version_part must be one of 'major', 'minor', or 'patch'. Got '{version_part}'." - ) - raise typer.Exit(code=1) - - print( - f"Bumping {version_part} version for packages matching regex: '{package_regex}'" - ) - - current_path = Path(".") - for ato_file in current_path.rglob("**/ato.yaml"): - try: - ato_yaml = ato_file.read_text() - data = yaml.load(ato_yaml, Loader=yaml.FullLoader) - - package = data.get("package") - if not package: - print(f"Skipping {ato_file}, no package found.") - continue - - package_name = package.get("identifier", "").split("/")[-1] - if not package_name: - print(f"Skipping {ato_file}, no package name found.") - continue - - if not re.match(package_regex, package_name): - print( - f"Skipping {ato_file} (name: {package_name}) due to regex mismatch or missing name." - ) - continue - - version_str = package.get("version") - if not version_str: - print(f"Skipping {ato_file}, no version found.") - continue - - major, minor, patch_val = map(int, version_str.split(".")) - - if version_part == "major": - major += 1 - minor = 0 - patch_val = 0 - elif version_part == "minor": - minor += 1 - patch_val = 0 - elif version_part == "patch": - patch_val += 1 - - new_version_str = f"{major}.{minor}.{patch_val}" - package["version"] = new_version_str - - # A bit ghetto, but doesn't fuck with the formatting - ato_yaml = ato_yaml.replace(f'"{version_str}"', f'"{new_version_str}"') - if not dry_run: - ato_file.write_text(ato_yaml) - - print(f"āœ… {version_str} -> {new_version_str} | {package_name} ") - - except Exception as e: - print(f"Error processing {ato_file}: {e}") - - -if __name__ == "__main__": - app() diff --git a/scripts/check_status.py b/scripts/check_status.py deleted file mode 100644 index e6ad71298..000000000 --- a/scripts/check_status.py +++ /dev/null @@ -1,260 +0,0 @@ -#! uv run -# /// script -# dependencies = [ -# "typer>=0.12", -# "typing_extensions>=4.10.0", -# "rich>=13.0.0", -# "pandas>=2.0.0", -# ] -# /// - -import re -import time -import subprocess -from concurrent.futures import ProcessPoolExecutor, as_completed -from pathlib import Path -import typer -from rich.console import Console, Group -from rich.table import Table -from rich.live import Live - -app = typer.Typer() - -console = Console() - - -def build_and_verify( - package_dir: Path, args: tuple -) -> tuple[ - str, - bool, - bool, - float, - int, - str, - str, - int | None, - str | None, - str | None, -]: - """ - Runs 'ato build --keep-picked-parts' then 'ato package verify' for a package. - Returns: (package_name, build_success, verify_success, build_seconds) - """ - package_name = package_dir.name - - # Build - build_start = time.perf_counter() - build_proc = subprocess.run( - ["ato", "build", "--keep-picked-parts"] + list(args), - cwd=package_dir, - capture_output=True, - text=True, - check=False, - ) - build_success = build_proc.returncode == 0 - build_seconds = max(0.0, time.perf_counter() - build_start) - - # Verify (only if build ok) - verify_success = False - verify_rc: int | None = None - verify_stdout: str | None = None - verify_stderr: str | None = None - if build_success: - verify_proc = subprocess.run( - ["ato", "package", "verify"], - cwd=package_dir, - capture_output=True, - text=True, - check=False, - ) - verify_rc = verify_proc.returncode - verify_stdout = verify_proc.stdout - verify_stderr = verify_proc.stderr - verify_success = verify_rc == 0 - - return ( - package_name, - build_success, - verify_success, - build_seconds, - build_proc.returncode, - build_proc.stdout, - build_proc.stderr, - verify_rc, - verify_stdout, - verify_stderr, - ) - - -@app.command() -def main( - args: list[str] = typer.Argument(None, help="Arguments to pass to ato build"), - package_regex: str = typer.Option(None, help="Regex to filter packages to build"), -): - """Builds and verifies all packages in the 'packages' directory in parallel.""" - original_dir = Path.cwd() - packages_dir = Path("packages") - - if not packages_dir.is_dir(): - console.print( - f"[red]āŒ Error: 'packages' directory not found in {original_dir}[/red]" - ) - return - - package_subdirs = [d for d in packages_dir.iterdir() if d.is_dir()] - - if not package_subdirs: - console.print(f"[yellow]No packages found in {packages_dir}[/yellow]") - return - - if package_regex: - package_subdirs = [ - d for d in package_subdirs if re.match(package_regex, d.name) - ] - - build_args = tuple(args) if args else () - - # Accumulator for results and DataFrame - results_rows: list[dict] = [] - - def make_summary_tables() -> Group: - # Totals - build_fail = sum(1 for r in results_rows if r["build_success"] is False) - verify_fail = sum( - 1 for r in results_rows if r["build_success"] and not r["verify_success"] - ) - pass_both = sum( - 1 for r in results_rows if r["build_success"] and r["verify_success"] - ) - - # Top summary table with counts in headers - summary = Table(show_header=True, header_style="bold magenta") - summary.add_column(f"Fails Build ({build_fail})", justify="center") - summary.add_column(f"Fails Verify ({verify_fail})", justify="center") - summary.add_column(f"Passes Both ({pass_both})", justify="center") - summary.add_row( - f"[red]{build_fail}[/red]", - f"[yellow]{verify_fail}[/yellow]", - f"[green]{pass_both}[/green]", - ) - - # Detailed per-package table - detail = Table(show_header=True, header_style="bold cyan") - detail.add_column("Package", overflow="fold") - detail.add_column("Build", justify="center") - detail.add_column("Verify", justify="center") - detail.add_column("Build Time (s)", justify="right") - - # Sort by build time (descending: slowest first) - for r in sorted(results_rows, key=lambda x: x["build_seconds"], reverse=True): - build_cell = ( - "[green]PASS[/green]" if r["build_success"] else "[red]FAIL[/red]" - ) - if not r["build_success"]: - verify_cell = "[dim]-[/dim]" - else: - verify_cell = ( - "[green]PASS[/green]" if r["verify_success"] else "[red]FAIL[/red]" - ) - detail.add_row( - r["package_name"], - build_cell, - verify_cell, - f"{r['build_seconds']:.1f}", - ) - - return Group(summary, detail) - - with Live(console=console, refresh_per_second=8) as live: - with ProcessPoolExecutor() as executor: - futures = { - executor.submit(build_and_verify, subdir, build_args): subdir - for subdir in package_subdirs - } - for future in as_completed(futures): - ( - package_name, - build_ok, - verify_ok, - build_secs, - build_rc, - build_out, - build_err, - verify_rc, - verify_out, - verify_err, - ) = future.result() - results_rows.append( - { - "package_name": package_name, - "build_success": build_ok, - "verify_success": verify_ok, - "build_seconds": build_secs, - "build_rc": build_rc, - "build_stdout": build_out, - "build_stderr": build_err, - "verify_rc": verify_rc, - "verify_stdout": verify_out, - "verify_stderr": verify_err, - } - ) - # Update live tables - live.update(make_summary_tables()) - - # Construct DataFrame (optional) and save to CSV if pandas is available - try: - pd = __import__("pandas") - df = pd.DataFrame( - results_rows, - columns=[ - "package_name", - "build_success", - "verify_success", - "build_seconds", - ], - ) - out_csv = Path("build_status.csv") - df.to_csv(out_csv, index=False) - console.print(f"[dim]Saved build status to {out_csv.resolve()}[/dim]") - except Exception: - console.print( - "[yellow]pandas not available; skipping DataFrame export[/yellow]" - ) - - # Exit with non-zero if any failed build or failed verify; print detailed logs - any_failed = any( - (not r["build_success"]) or (r["build_success"] and not r["verify_success"]) - for r in results_rows - ) - if any_failed: - console.rule("[bold red]Failure Details") - for r in sorted(results_rows, key=lambda x: x["package_name"].lower()): - if not r["build_success"]: - console.print( - f"[red]āŒ {r['package_name']} – Build failed (rc={r.get('build_rc')})[/red]" - ) - if r.get("build_stderr"): - console.print("[bold]stderr:[/bold]") - console.print(r["build_stderr"]) - if r.get("build_stdout"): - console.print("[bold]stdout:[/bold]") - console.print(r["build_stdout"]) - console.print() - continue - if r["build_success"] and not r["verify_success"]: - console.print( - f"[yellow]āš ļø {r['package_name']} – Verify failed (rc={r.get('verify_rc')})[/yellow]" - ) - if r.get("verify_stderr"): - console.print("[bold]stderr:[/bold]") - console.print(r["verify_stderr"]) - if r.get("verify_stdout"): - console.print("[bold]stdout:[/bold]") - console.print(r["verify_stdout"]) - console.print() - raise typer.Exit(code=1) - - -if __name__ == "__main__": - app() diff --git a/scripts/discover-changed-packages.sh b/scripts/discover-changed-packages.sh deleted file mode 100755 index bea0ec767..000000000 --- a/scripts/discover-changed-packages.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -find_all_packages() { - find packages -mindepth 2 -maxdepth 2 -name "ato.yaml" -type f | \ - sed 's|/ato.yaml||' | \ - sort -} - -find_changed_packages() { - local base_ref="$1" - local changed_files=$( - git diff \ - --name-only \ - "$base_ref"...HEAD \ - -- \ - ':/packages/**' \ - ':(exclude)packages/archive/**' \ - 2>/dev/null || echo "" - ) - - if [[ -z "$changed_files" ]]; then - return - fi - - echo "$changed_files" | \ - grep -E '^packages/[^/]+/' | \ - cut -d'/' -f1,2 | \ - sort -u | \ - while read -r package_dir; do - if [[ -d "$package_dir" ]]; then - echo "$package_dir" - fi - done -} - -to_json_array() { - local input="$1" - - if [[ -z "$input" ]]; then - echo "[]" - else - jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))' <<< "$input" - fi -} - -main() { - local event_name="${1:-}" - local base_ref="${2:-}" - local packages_json="" - - case "$event_name" in - "pull_request"|"push") - if [[ -z "$base_ref" ]]; then - echo "Error: base_ref is required for $event_name events" >&2 - exit 1 - fi - - if [[ "$base_ref" =~ ^0+$ ]]; then - echo "Initial commit detected, building all packages" >&2 - packages=$(find_all_packages) - packages_json=$(to_json_array "$packages") - else - changed_packages=$(find_changed_packages "$base_ref") - - packages_json=$(to_json_array "$changed_packages") - fi - ;; - - "workflow_dispatch"|*) - echo "Building all packages (event: ${event_name:-unknown})" >&2 - packages=$(find_all_packages) - packages_json=$(to_json_array "$packages") - ;; - esac - - echo "$packages_json" - - echo "Discovered packages: $packages_json" >&2 -} - -main "$@" diff --git a/scripts/package_info.py b/scripts/package_info.py deleted file mode 100644 index fa03fb0d2..000000000 --- a/scripts/package_info.py +++ /dev/null @@ -1,232 +0,0 @@ -#! uv run -# /// script -# dependencies = [ -# "typer>=0.12", -# "typing_extensions>=4.10.0", -# "rich>=13.0.0", -# "pyyaml>=6.0.2", -# ] -# /// - -from enum import StrEnum - -import typer -from typing_extensions import Annotated -from typing import Optional -from pathlib import Path -import re -import yaml -import subprocess -from datetime import datetime -from rich.console import Console -from rich.table import Table - -app = typer.Typer() -console = Console() - - -class SortBy(StrEnum): - package = "package" - version = "version" - requires_atopile = "requires-atopile" - last_commit = "last-commit" - dependencies = "dependencies" - - -def get_last_commit_date(package_path: Path) -> str: - """Get the last commit date for a package directory.""" - try: - result = subprocess.run( - [ - "git", - "log", - "--pretty=format:%ad", - "--date=iso", - "--", - str(package_path), - ], - capture_output=True, - text=True, - cwd=package_path.parent.parent, # Go up to packages root - check=True, - ) - if result.stdout.strip(): - # Parse the ISO date and format it nicely - date_str = result.stdout.strip().split("\n")[0] - dt = datetime.fromisoformat(date_str.replace(" +", "+").replace(" -", "-")) - return dt.strftime("%Y-%m-%d %H:%M:%S") - return "Unknown" - except subprocess.CalledProcessError: - return "Unknown" - - -def format_dependencies_markdown(deps: str) -> str: - """Format dependencies for markdown (replace newlines with
)""" - if deps == "": - return "" - return deps.replace("\n", "
") - - -def extract_dependencies(package_dir: Path, package_name: str) -> str: - """Extract dependencies from .ato files in the package.""" - dependencies = set() - - # Look for .ato files in the package directory - for ato_file in package_dir.glob("*.ato"): - try: - with open(ato_file, "r", encoding="utf-8") as f: - content = f.read() - - # Find "from" imports that reference atopile packages - from_imports = re.findall(r'from\s+"(atopile/[^"]+)"', content) - for imp in from_imports: - # Extract package name from path like "atopile/package-name/file.ato" - dep_package_name = imp.split("/")[1] - # Exclude the package itself from dependencies - if dep_package_name != package_name: - dependencies.add(dep_package_name) - - except Exception: - continue - - if dependencies: - return "\n".join(sorted(dependencies)) - return "" - - -@app.command() -def main( - package_regex: Annotated[ - str, - typer.Option("--package-regex", "-p", help="Regex to filter packages list"), - ] = ".*", - markdown_output: Annotated[ - Optional[Path], - typer.Option("--markdown-output", "-m", help="Path to export markdown file"), - ] = None, - sort_by: Annotated[ - SortBy, - typer.Option("--sort-by", "-s", help="Column to sort by"), - ] = SortBy.package, - reverse: Annotated[ - bool, - typer.Option("--reverse", "-r", help="Reverse sort order"), - ] = False, -): - """ - Prints a formatted table of packages with: - - package name - - version - - requires-atopile version - - date and time of last commit to the package - - list of dependencies (other packages) - """ - packages_root = Path(__file__).parent.parent / "packages" - - if not packages_root.exists(): - typer.echo("Error: packages directory not found", err=True) - raise typer.Exit(1) - - # Collect package information - packages_info = [] - - for package_dir in sorted(packages_root.iterdir()): - if not package_dir.is_dir() or package_dir.name.startswith("."): - continue - - # Check if package name matches regex - if not re.search(package_regex, package_dir.name): - continue - - ato_yaml_path = package_dir / "ato.yaml" - if not ato_yaml_path.exists(): - continue - - try: - with open(ato_yaml_path, "r", encoding="utf-8") as f: - config = yaml.safe_load(f) - - package_info = config.get("package", {}) - package_name = package_info.get("identifier", package_dir.name) - if package_name.startswith("atopile/"): - package_name = package_name[8:] # Remove 'atopile/' prefix - - version = package_info.get("version", "Unknown") - requires_atopile = config.get("requires-atopile", "Unknown") - last_commit = get_last_commit_date(package_dir) - dependencies = extract_dependencies(package_dir, package_name) - - packages_info.append( - { - "name": package_name, - "version": version, - "requires_atopile": requires_atopile, - "last_commit": last_commit, - "dependencies": dependencies, - } - ) - - except Exception as e: - typer.echo(f"Error processing {package_dir.name}: {e}", err=True) - continue - - if not packages_info: - typer.echo("No packages found matching the criteria.") - return - - # Sort packages - sort_keys = { - SortBy.package: lambda p: p["name"].lower(), - SortBy.version: lambda p: (p["version"], p["name"].lower()), - SortBy.requires_atopile: lambda p: (p["requires_atopile"], p["name"].lower()), - SortBy.last_commit: lambda p: (p["last_commit"], p["name"].lower()), - SortBy.dependencies: lambda p: ( - len(p["dependencies"].split("\n")) if p["dependencies"] else 0, - p["name"].lower(), - ), - } - packages_info.sort(key=sort_keys[sort_by], reverse=reverse) - - # Create Rich table - table = Table(title=f"Package Information ({len(packages_info)} packages)") - - table.add_column("Package", style="cyan", no_wrap=True) - table.add_column("Version", style="magenta") - table.add_column("Requires Atopile", style="green") - table.add_column("Last Commit", style="yellow") - table.add_column("Dependencies", style="blue", no_wrap=True) - - for pkg in packages_info: - table.add_row( - pkg["name"], - pkg["version"], - pkg["requires_atopile"], - pkg["last_commit"], - pkg["dependencies"], - ) - - # Export to markdown if requested - if markdown_output: - with open(markdown_output, "w", encoding="utf-8") as f: - f.write(f"# Package Information ({len(packages_info)} packages)\n\n") - f.write( - "| Package | Version | Requires Atopile | Last Commit | Dependencies |\n" - ) - f.write( - "|---------|---------|------------------|-------------|--------------|\n" - ) - - for pkg in packages_info: - deps_md = format_dependencies_markdown(pkg["dependencies"]) - f.write( - f"| {pkg['name']} | {pkg['version']} | {pkg['requires_atopile']} | {pkg['last_commit']} | {deps_md} |\n" - ) - - typer.echo(f"Exported package information to {markdown_output}") - - # Always print to console - console.print(table) - - -if __name__ == "__main__": - app() diff --git a/scripts/review_modified_pcbs.py b/scripts/review_modified_pcbs.py deleted file mode 100755 index 972af61de..000000000 --- a/scripts/review_modified_pcbs.py +++ /dev/null @@ -1,204 +0,0 @@ -#! uv run -# /// script -# dependencies = [ -# "typer>=0.12", -# "rich>=13.0.0", -# ] -# /// - -import subprocess -import sys -import os -from pathlib import Path -import typer -from rich.console import Console -from rich.prompt import Confirm -from rich.table import Table -from rich.panel import Panel - -app = typer.Typer() -console = Console() - - -def get_modified_pcb_files(base_branch: str = "origin/main") -> list[Path]: - """Get all modified .kicad_pcb files compared to the base branch.""" - try: - # Get all modified files - result = subprocess.run( - ["git", "diff", "--name-only", f"{base_branch}...HEAD"], - capture_output=True, - text=True, - check=True, - ) - - # Filter for .kicad_pcb files - all_files = result.stdout.strip().split("\n") - pcb_files = [ - Path(f) for f in all_files if f.endswith(".kicad_pcb") and f.strip() - ] - - # Filter to only existing files (in case some were deleted) - existing_pcb_files = [f for f in pcb_files if f.exists()] - - return existing_pcb_files - - except subprocess.CalledProcessError as e: - console.print(f"[red]Error getting git diff: {e}[/red]") - return [] - - -def open_pcb_file(file_path: Path) -> bool: - """Open a PCB file with the default application.""" - try: - if sys.platform == "darwin": # macOS - subprocess.run(["open", str(file_path)], check=True) - elif sys.platform == "linux": - subprocess.run(["xdg-open", str(file_path)], check=True) - elif sys.platform == "win32": - os.startfile(str(file_path)) - else: - console.print(f"[yellow]Unsupported platform: {sys.platform}[/yellow]") - console.print(f"Please manually open: {file_path}") - return False - return True - except subprocess.CalledProcessError as e: - console.print(f"[red]Error opening file {file_path}: {e}[/red]") - return False - except Exception as e: - console.print(f"[red]Unexpected error opening file {file_path}: {e}[/red]") - return False - - -@app.command() -def main( - base_branch: str = typer.Option( - "origin/main", "--base", "-b", help="Base branch to compare against" - ), - auto_open: bool = typer.Option( - False, - "--auto-open", - "-a", - help="Automatically open all files without prompting", - ), -): - """ - Find modified .kicad_pcb files in the current branch and interactively open them for review. - - This script will: - 1. Find all .kicad_pcb files modified compared to the base branch - 2. Display them in a table - 3. Allow you to open each one by pressing Enter (or skip with any other key) - """ - - # Check if we're in a git repository - try: - subprocess.run( - ["git", "rev-parse", "--git-dir"], capture_output=True, check=True - ) - except subprocess.CalledProcessError: - console.print("[red]āŒ Error: Not in a git repository[/red]") - raise typer.Exit(1) - - console.print( - f"[bold blue]šŸ” Finding modified .kicad_pcb files compared to {base_branch}...[/bold blue]" - ) - - pcb_files = get_modified_pcb_files(base_branch) - - if not pcb_files: - console.print("[yellow]No modified .kicad_pcb files found.[/yellow]") - return - - # Display summary table - table = Table( - title="Modified PCB Files", show_header=True, header_style="bold magenta" - ) - table.add_column("File", style="cyan", overflow="fold") - table.add_column("Package", style="green") - table.add_column("Size", justify="right", style="dim") - - for pcb_file in pcb_files: - # Extract package name (assuming structure: packages/package-name/...) - parts = pcb_file.parts - package_name = "unknown" - if len(parts) >= 2 and parts[0] == "packages": - package_name = parts[1] - - # Get file size - try: - size = pcb_file.stat().st_size - size_str = f"{size:,} bytes" - except: - size_str = "unknown" - - table.add_row(str(pcb_file), package_name, size_str) - - console.print(table) - console.print() - - if auto_open: - console.print("[bold yellow]Auto-opening all files...[/bold yellow]") - for pcb_file in pcb_files: - console.print(f"Opening: [cyan]{pcb_file}[/cyan]") - open_pcb_file(pcb_file) - return - - # Interactive review - console.print( - Panel.fit( - "[bold]Interactive Review Mode[/bold]\n\n" - "For each file:\n" - "• Press [green]Enter[/green] to open the file\n" - "• Press [red]any other key[/red] to skip\n" - "• Press [yellow]Ctrl+C[/yellow] to exit", - border_style="blue", - ) - ) - console.print() - - opened_count = 0 - skipped_count = 0 - - try: - for i, pcb_file in enumerate(pcb_files, 1): - console.print( - f"[bold]File {i}/{len(pcb_files)}:[/bold] [cyan]{pcb_file}[/cyan]" - ) - - try: - # Wait for user input - user_input = input("Press Enter to open (any other key to skip): ") - - if user_input == "": # Enter was pressed - console.print(f"[green]Opening {pcb_file}...[/green]") - if open_pcb_file(pcb_file): - opened_count += 1 - else: - console.print("[red]Failed to open file[/red]") - else: - console.print("[yellow]Skipped[/yellow]") - skipped_count += 1 - - except KeyboardInterrupt: - console.print("\n[yellow]Interrupted by user[/yellow]") - break - - console.print() - - except KeyboardInterrupt: - console.print("\n[yellow]Interrupted by user[/yellow]") - - # Summary - console.print( - Panel.fit( - f"[bold]Review Summary[/bold]\n\n" - f"Files found: {len(pcb_files)}\n" - f"Files opened: [green]{opened_count}[/green]\n" - f"Files skipped: [yellow]{skipped_count}[/yellow]", - border_style="green", - ) - ) - - -if __name__ == "__main__": - app()