# Sviluppo Setup locale, pytest, ruff, e come estendere il client. --- ## Setup ```bash git clone https://192.168.0.183:8085/truetype/progetto2.git cd progetto2 python3 -m venv .venv source .venv/bin/activate pip install -e ".[dev]" ``` Lo sviluppo quotidiano è Python puro; nessun build system esterno (setuptools è trasparente via `pyproject.toml`). --- ## Test ```bash pytest # 66+ test (telnet + ansi + deadsouls + mapper + logger) pytest tests/test_logger.py -v # solo il logger (più verboso) pytest -k "test_rolling_parser" # filtrato per nome pytest --tb=short # traceback corti ``` I test usano `pytest.ini_options = ["-q"]` di default in `pyproject.toml`. ### Cosa è coperto | File | Copre | | --- | --- | | `test_ansi.py` | stripper ANSI + `visible_len` | | `test_telnet.py` | IACParser (state machine completa) + TelnetConnection (mock asyncio) | | `test_deadsouls.py` | `parse_exits` + `RollingRoomParser` scenario multi‑chunk | | `test_mapper.py` | `Mapper.visit`, link, layout BFS, orphan placement, render | | `test_logger.py` | EventLogger round‑trip JSONL, MD rendering per ogni `kind`, redazione password, idempotenza `close()`, lone‑ESC safety | ### Cosa *non* è coperto - `cli.py` end‑to‑end (richiede telnet server; il vero test è una smoke run contro il docker locale di Dead Souls). - `mapper._render_vertical_row` (casi degeneri con celle non continue; non aggiunti perché non osservati in pratica). - Re‑entrata del logger in un worker thread (lock difensivo è documentato ma non testato). --- ## Lint ```bash ruff check src tests ruff format src tests ``` Config in `pyproject.toml`: ```toml [tool.ruff] line-length = 100 target-version = "py310" ``` (Aggiungi regole strict `select = ["E", "F", "I", "B", "UP"]` se vuoi enforcement.) --- ## Smoke test end‑to‑end Dead Souls docker su `localhost:6666`: ```bash # 2 secondi, log JSONL + MD LOGJSON=$(mktemp /tmp/mud-json.XXXXXX.jsonl) LOGMD=$(mktemp /tmp/mud-md.XXXXXX.md) timeout 2 python -m progetto2 --host localhost --port 6666 \ --log-json "$LOGJSON" --log-md "$LOGMD" /dev/null 2>&1 || true wc -l "$LOGJSON" "$LOGMD" head -3 "$LOGJSON" head -8 "$LOGMD" ``` Aspettative: almeno 2‑3 righe JSONL (session_start, text, session_end) e un header MD con `host/port/user`. --- ## Come aggiungere un modulo Esempio: nuovo modulo `directions.py` che assorbe `EXIT_ALIASES` da `deadsouls.py`. Procedura: 1. Crea `src/progetto2/directions.py` con le costanti e `normalize_exits()`. 2. Aggiorna `deadsouls.py` → re‑import da `directions` per back‑compat. 3. Aggiorna `gmcp.py`, `mapper.py`, `cli.py` → import da `directions`. 4. Scrivi `tests/test_directions.py`. 5. Aggiorna questo file (`docs/development.md`) e `docs/modules.md`. 6. `pytest` + `ruff check`. ### Principi - **Foglia pura sopra orchestratore.** Solo `cli.py` può dipendere da più moduli. Mai il contrario. - **No mutazione cross‑module.** Ogni modulo possiede il suo stato. - **Mai dipendere dal payload GMCP da mapper o deadsouls.** Sono moduli di testo puro. - **Mai import `cli` da moduli bassi.** Solo `cli` sa di `prompt_toolkit`. --- ## Debugging tipico | Sintomo | Tool | | --- | --- | | IAC parser perso un byte | `pytest tests/test_telnet.py -v` + aggiungere print in `_byte()` | | GMCP module non riconosciuto | leggere `gmcp.parse_gmcp` con `data=b"..."` in REPL | | Stanze non rilevate senza GMCP | stampare `state.parser._buf` dopo feed_text | | Mapper collassa in `[mappa troppo larga]` | abbassare `max_cols`/`max_rows` in `render(...)` | | Stanza GMCP arriva ma render non aggiorna | `state.mapper.current_room` punta ancora al vecchio? | | Password finisce nel log | grep `--log-md` per `hunter2`/`***` ratio | | Sessione hung dopo quit | `conn.close()` chiamato? `finally` cleanup? | --- ## Aggiungere un nuovo comando slash 1. In `cli.py:_handle_local_command`, aggiungi un ramo `elif cmd == "foo":`. 2. Se modifica stato → metti la logica prima del `if state.logger is not None: state.logger.local_cmd(...)`. 3. Se inoltra al MUD → usa `await conn.send_line("foo")`. 4. Aggiungi voce nella tabella di `docs/usage.md` e `_print_help()`. 5. Test: aggiungi in `tests/test_cli.py` (futuro; ancora non esiste). --- ## Versioning `__version__` in `__init__.py` (`0.1.0` in v0.2). Bump: - **patch** (0.1.x): bug fix, no nuovi flag. - **minor** (0.x.0): nuovi flag, nuove feature senza break della CLI. - **major** (x.0.0): break di `--log-*` schema eventi o rename simboli pubblici dei moduli. --- ## CI minima (Gitea Actions, opzionale) Esempio `.gitea/workflows/ci.yml`: ```yaml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.10" - run: python -m pip install -e ".[dev]" - run: pytest - run: ruff check src tests ``` Adatta ai vincoli del tuo runner Gitea (alcuni runner non hanno `actions/setup-python@v5`; pinning a versioni disponibili è tipico).