Initial commit: MUD client v0.2

This commit is contained in:
truetype committed 2026-07-07 15:38:28 +00:00
commit 0030fd7d82
26 files changed
+4421

No files matched your search

+174
View File
@@ -0,0 +1,174 @@
# 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 >/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).