5.0 KiB
Sviluppo
Setup locale, pytest, ruff, e come estendere il client.
Setup
git clone https://192.168.0.183:8085/truetype/viandante.git
cd viandante
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
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.pyend‑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
ruff check src tests
ruff format src tests
Config in pyproject.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:
# 2 secondi, log JSONL + MD
LOGJSON=$(mktemp /tmp/mud-json.XXXXXX.jsonl)
LOGMD=$(mktemp /tmp/mud-md.XXXXXX.md)
timeout 2 python -m viandante --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:
- Crea
src/viandante/directions.pycon le costanti enormalize_exits(). - Aggiorna
deadsouls.py→ re‑import dadirectionsper back‑compat. - Aggiorna
gmcp.py,mapper.py,cli.py→ import dadirections. - Scrivi
tests/test_directions.py. - Aggiorna questo file (
docs/development.md) edocs/modules.md. pytest+ruff check.
Principi
- Foglia pura sopra orchestratore. Solo
cli.pypuò 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
clida moduli bassi. Soloclisa diprompt_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
- In
cli.py:_handle_local_command, aggiungi un ramoelif cmd == "foo":. - Se modifica stato → metti la logica prima del
if state.logger is not None: state.logger.local_cmd(...). - Se inoltra al MUD → usa
await conn.send_line("foo"). - Aggiungi voce nella tabella di
docs/usage.mde_print_help(). - 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:
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).