diff --git a/README.md b/README.md index 6ce4f36..776302f 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,18 @@ Run the optimizer plus baselines: repixelize compare input.png --out output.png --diagnostics-dir diagnostics ``` +Run the GUI from the repo checkout: + +```powershell +.\scripts\run_gui.ps1 +``` + +If you want to bypass the PowerShell wrapper and call Python directly: + +```powershell +.\.venv\Scripts\python scripts\run_gui.py +``` + ## Corpus And Benchmarks Prepare a local Creative Commons corpus: diff --git a/scripts/run_gui.ps1 b/scripts/run_gui.ps1 new file mode 100644 index 0000000..118676b --- /dev/null +++ b/scripts/run_gui.ps1 @@ -0,0 +1,25 @@ +param( + [string]$BindHost = "127.0.0.1", + [int]$Port = 8000, + [switch]$Reload +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$python = Join-Path $repoRoot ".venv\Scripts\python.exe" +if (-not (Test-Path $python)) { + $python = "python" +} + +$args = @( + (Join-Path $repoRoot "scripts\run_gui.py"), + "--host", $BindHost, + "--port", $Port +) + +if ($Reload) { + $args += "--reload" +} + +& $python @args diff --git a/scripts/run_gui.py b/scripts/run_gui.py new file mode 100644 index 0000000..2749188 --- /dev/null +++ b/scripts/run_gui.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + + +def _repo_root() -> Path: + return Path(__file__).resolve().parents[1] + + +def _ensure_src_on_path() -> None: + src = _repo_root() / "src" + src_text = str(src) + if src_text not in sys.path: + sys.path.insert(0, src_text) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Launch the Repixelizer GUI from the repo checkout.") + parser.add_argument("--host", default="127.0.0.1", help="Host interface to bind") + parser.add_argument("--port", type=int, default=8000, help="Port to bind") + parser.add_argument("--reload", action="store_true", help="Enable autoreload while developing the GUI server") + return parser + + +def main(argv: list[str] | None = None) -> int: + _ensure_src_on_path() + from repixelizer.gui import main as gui_main + + args = build_parser().parse_args(argv) + return gui_main(host=args.host, port=args.port, reload=args.reload) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_gui.py b/tests/test_gui.py index e82ac3b..066b6dc 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -1,5 +1,8 @@ from __future__ import annotations +import importlib.util +from pathlib import Path + from repixelizer.pipeline import run_pipeline_rgba from repixelizer.synthetic import fake_pixelize, make_emblem @@ -35,3 +38,26 @@ def test_run_pipeline_rgba_emits_observer_events_for_gui() -> None: assert "phase_field_initial" in events assert events.count("phase_field_step") == 2 assert events[-3:] == ["cleanup_completed", "palette_completed", "pipeline_completed"] + + +def test_repo_gui_runner_dispatches_to_gui_main(monkeypatch) -> None: + repo_root = Path(__file__).resolve().parents[1] + script_path = repo_root / "scripts" / "run_gui.py" + spec = importlib.util.spec_from_file_location("repixelizer_run_gui_script", script_path) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + called = {} + + def fake_gui_main(*, host: str, port: int, reload: bool) -> int: + called["host"] = host + called["port"] = port + called["reload"] = reload + return 0 + + monkeypatch.setattr("repixelizer.gui.main", fake_gui_main) + exit_code = module.main(["--host", "127.0.0.1", "--port", "8123", "--reload"]) + assert exit_code == 0 + assert called == {"host": "127.0.0.1", "port": 8123, "reload": True}