200 lines
5.9 KiB
Bash
Executable file
200 lines
5.9 KiB
Bash
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import argparse
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.table import Table
|
|
|
|
console = Console()
|
|
|
|
|
|
def info(msg: str) -> None:
|
|
console.print(f"• {msg}")
|
|
|
|
|
|
def ok(msg: str) -> None:
|
|
console.print(f"[green]✔ {msg}[/]")
|
|
|
|
|
|
def warn(msg: str) -> None:
|
|
console.print(f"[yellow]▲ {msg}[/]")
|
|
|
|
|
|
def die(msg: str, code: int = 1) -> "None":
|
|
console.print(f"[bold red]✖ {msg}[/]")
|
|
sys.exit(code)
|
|
|
|
|
|
def ensure_project_layout() -> None:
|
|
missing: List[str] = []
|
|
if not Path("robot_class.py").is_file():
|
|
missing.append("robot_class.py")
|
|
if not Path(".scripts").is_dir():
|
|
missing.append(".scripts/")
|
|
if missing:
|
|
die("Non sei nella cartella giusta: mancano " + ", ".join(missing), code=9)
|
|
# errore custom separato per robot_local.py (richiesta utente)
|
|
if not Path("robot_local.py").is_file():
|
|
die(
|
|
"File personale 'robot_local.py' non trovato. "
|
|
"Crealo (es. copia 'robot_local.example.py') e riprova.",
|
|
code=5,
|
|
)
|
|
|
|
|
|
def find_matching_paren(s: str, open_idx: int) -> int | None:
|
|
"""Trova la ')' che chiude la '(' a open_idx. Ignora stringhe e backslash."""
|
|
depth = 0
|
|
i = open_idx
|
|
in_str = False
|
|
quote: str | None = None
|
|
while i < len(s):
|
|
ch = s[i]
|
|
if in_str:
|
|
if ch == "\\":
|
|
i += 2
|
|
continue
|
|
if ch == quote:
|
|
in_str = False
|
|
i += 1
|
|
continue
|
|
if ch in ("'", '"'):
|
|
in_str = True
|
|
quote = ch
|
|
i += 1
|
|
continue
|
|
if ch == "(":
|
|
depth += 1
|
|
elif ch == ")":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return i
|
|
i += 1
|
|
return None
|
|
|
|
|
|
def parse_robots_py(robots_py: Path) -> Dict[str, str]:
|
|
"""
|
|
Ritorna dict {var_name: hub_name} leggendo definizioni tipo:
|
|
cbrobot = LazyRobot(..., name="cbhub", ...)
|
|
Parsing robusto: trova inizio 'X = LazyRobot(' e bilancia parentesi.
|
|
"""
|
|
if not robots_py.exists():
|
|
die(f"File non trovato: {robots_py}", code=3)
|
|
|
|
text = robots_py.read_text(encoding="utf-8")
|
|
|
|
start_rx = re.compile(r"^\s*(?P<var>[A-Za-z_]\w*)\s*=\s*LazyRobot\s*\(", re.MULTILINE)
|
|
name_rx = re.compile(r"\bname\s*=\s*(['\"])(?P<hub>.+?)\1", re.DOTALL)
|
|
|
|
mapping: Dict[str, str] = {}
|
|
|
|
for m in start_rx.finditer(text):
|
|
var = m.group("var")
|
|
open_idx = m.end() - 1 # '('
|
|
close_idx = find_matching_paren(text, open_idx)
|
|
if close_idx is None:
|
|
continue
|
|
body = text[open_idx + 1 : close_idx]
|
|
m_name = name_rx.search(body)
|
|
if not m_name:
|
|
continue
|
|
hub = m_name.group("hub")
|
|
mapping[var] = hub
|
|
|
|
if not mapping:
|
|
die("Nessun robot trovato in robots.py (pattern LazyRobot(..., name=...)).", code=3)
|
|
|
|
return mapping
|
|
|
|
|
|
def choose_robot(mapping: Dict[str, str], ident: str) -> Tuple[str, str]:
|
|
"""
|
|
Accetta come 'ident' o il nome variabile (cbrobot) o il nome hub (cbhub).
|
|
Ritorna (var_name, hub_name).
|
|
"""
|
|
ident_lc = ident.strip().lower()
|
|
# match per variabile
|
|
for var, hub in mapping.items():
|
|
if var.lower() == ident_lc:
|
|
return var, hub
|
|
# match per hub
|
|
for var, hub in mapping.items():
|
|
if hub.lower() == ident_lc:
|
|
return var, hub
|
|
# non trovato: mostra tabella e esci
|
|
warn(f"Robot '{ident}' non trovato in robots.py.")
|
|
table = Table(title="Robot conosciuti (da robots.py)")
|
|
table.add_column("Variabile", style="cyan", no_wrap=True)
|
|
table.add_column("Nome hub (BLE)", style="green")
|
|
for var, hub in mapping.items():
|
|
table.add_row(var, hub)
|
|
console.print(table)
|
|
sys.exit(4)
|
|
|
|
|
|
def rewrite_robot_local(robot_local: Path, var_name: str) -> None:
|
|
header = (
|
|
"# robot_local.py — generato automaticamente da pbd.py\n"
|
|
"# Non commitare questo file: deve essere nel .gitignore.\n"
|
|
)
|
|
body = f"from robots import {var_name} as robot\n"
|
|
try:
|
|
robot_local.write_text(header + body, encoding="utf-8")
|
|
except Exception as e: # pragma: no cover (best-effort)
|
|
die(f"Impossibile scrivere {robot_local}: {e}")
|
|
|
|
|
|
def run_pybricksdev(file_path: Path, hub_name: str) -> int:
|
|
if not file_path.exists():
|
|
die(f"File target da inviare non trovato: {file_path}", code=6)
|
|
exe = shutil.which("pybricksdev")
|
|
if exe is None:
|
|
die("Comando 'pybricksdev' non trovato nel PATH. Installalo e riprova.", code=7)
|
|
cmd = [exe, "run", "ble", str(file_path), "--name", hub_name]
|
|
return subprocess.run(cmd, check=False).returncode
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
p = argparse.ArgumentParser(
|
|
prog="pbd.py",
|
|
description="Imposta robot_local.py e lancia: pybricksdev run ble <file> --name <hub>",
|
|
)
|
|
p.add_argument("nomerobot", help="Variabile di robots.py (es. 'cbrobot') oppure nome hub BLE (es. 'cbhub').")
|
|
p.add_argument("nomefile", help="Percorso del file .py da eseguire sul hub.")
|
|
return p
|
|
|
|
|
|
def main() -> None:
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
ensure_project_layout()
|
|
|
|
mapping = parse_robots_py(Path("robots.py"))
|
|
var, hub = choose_robot(mapping, args.nomerobot)
|
|
|
|
info(f"Selezionato robot: [cyan]{var}[/] → hub BLE: [green]{hub}[/]")
|
|
|
|
rewrite_robot_local(Path("robot_local.py"), var)
|
|
ok(f"Aggiornato robot_local.py con 'from robots import {var} as robot'")
|
|
|
|
info(f"Eseguo: pybricksdev run ble {args.nomefile} --name {hub}")
|
|
rc = run_pybricksdev(Path(args.nomefile), hub)
|
|
|
|
if rc == 0:
|
|
ok("pybricksdev completato con successo.")
|
|
else:
|
|
die(f"pybricksdev terminato con exit code {rc}.", code=rc)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|