From 5821385dc838a1436c8a30803836b55b95a56a38 Mon Sep 17 00:00:00 2001 From: gitea-actions Date: Sat, 22 Aug 2026 16:12:16 +0200 Subject: [PATCH] =?UTF-8?q?=C4=8Ce=C5=A1tina=20na=20v=C3=BDpisu=20nesm?= =?UTF-8?q?=C3=AD=20shodit=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windowsová binárka spadla dřív, než stihla vypsat adresu: print("Počitadlo je dostupné na:") UnicodeEncodeError: 'charmap' codec can't encode character 'č' Windows sahá po historické kódové stránce všude, kde výstup není konzole — přesměrovaný do souboru, puštěný ze skriptu, ve frontě CI. První háček nad takovým proudem shodí celý program. Oba vstupní body si teď výstup přepnou na UTF-8. Na konzoli i na ostatních systémech je to prázdná operace, protože tam je UTF-8 dávno výchozí. Pojistka errors="replace" je pro zbytek případů: rozsypaný háček je pořád lepší než spadlý server. Chytila to zkouška po stavbě, ne uživatel. Test to reprodukuje přes TextIOWrapper s cp1252, takže se to bez opravy neprojde ani na Linuxu. Co-Authored-By: Claude Opus 5 --- README.md | 3 ++- console.py | 20 ++++++++++++++++++ dice.py | 2 ++ tests/test_console.py | 48 +++++++++++++++++++++++++++++++++++++++++++ web.py | 3 +++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 console.py create mode 100644 tests/test_console.py diff --git a/README.md b/README.md index 7af5121..4ad0c60 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ při remíze na prvním místě se hraje dál. | `stats.py` | síň slávy a kariérní statistiky | | `web.py` | Flask aplikace | | `dice.py` | totéž v terminálu, bez ukládání | +| `console.py` | aby čeština prošla i windowsovou konzolí | | `build.sh`, `kostky.spec` | stavba binárky | | `.gitea/workflows/` | testy a linuxová binárka doma | | `.github/workflows/` | binárky pro Windows a macOS | @@ -216,5 +217,5 @@ uv run --with-requirements requirements.txt --no-project pytest Nebo z připraveného prostředí prostě `pytest`. Nad každým pull requestem a nad `main` běží `.gitea/workflows/testy.yml` — -99 testů na Pythonu 3.11, 3.12, 3.13 i 3.14, a k tomu skriptovaná partie +102 testů na Pythonu 3.11, 3.12, 3.13 i 3.14, a k tomu skriptovaná partie v terminálové verzi, na kterou pytest nesahá. diff --git a/console.py b/console.py new file mode 100644 index 0000000..cf6a35d --- /dev/null +++ b/console.py @@ -0,0 +1,20 @@ +"""Aby čeština prošla i tam, kde konzole čeká něco jiného.""" + +import sys + + +def utf8() -> None: + """Přepne standardní výstup na UTF-8. + + Windows sahá po historické kódové stránce (cp1252, cp852) všude, kde + výstup není konzole — přesměrovaný do souboru, puštěný ze skriptu, ve + frontě CI. První háček nad takovým proudem shodí celý program, takže + binárka umřela dřív, než stihla vypsat adresu, na které poslouchá. + + Na konzoli i na ostatních systémech je UTF-8 dávno výchozí a tohle je + tam prázdná operace. `errors="replace"` je pojistka pro zbylé případy: + rozsypaný háček je pořád lepší než spadlý server. + """ + for stream in (sys.stdout, sys.stderr): + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="replace") diff --git a/dice.py b/dice.py index 19772ea..d05bf15 100644 --- a/dice.py +++ b/dice.py @@ -12,6 +12,7 @@ from os import name, system from tabulate import tabulate +import console from core import FINAL_SCORE, Game table_header = ["Hráč", "Skóre"] @@ -128,4 +129,5 @@ def main() -> None: if __name__ == "__main__": + console.utf8() main() diff --git a/tests/test_console.py b/tests/test_console.py new file mode 100644 index 0000000..f2fed1b --- /dev/null +++ b/tests/test_console.py @@ -0,0 +1,48 @@ +import io + +import console + + +class Proud: + """Náhrada za výstup, která si pamatuje, na co ji přepnuli.""" + + def __init__(self): + self.encoding = "cp1252" + self.errors = "strict" + + def reconfigure(self, encoding, errors): + self.encoding = encoding + self.errors = errors + + +def test_vystup_se_prepne_na_utf8(monkeypatch): + out, err = Proud(), Proud() + monkeypatch.setattr(console.sys, "stdout", out) + monkeypatch.setattr(console.sys, "stderr", err) + + console.utf8() + + assert (out.encoding, out.errors) == ("utf-8", "replace") + assert (err.encoding, err.errors) == ("utf-8", "replace") + + +def test_cestina_projde_i_pres_cp1252(monkeypatch): + """Přesně to, na čem umřela první windowsová binárka.""" + surovy = io.BytesIO() + proud = io.TextIOWrapper(surovy, encoding="cp1252") + monkeypatch.setattr(console.sys, "stdout", proud) + monkeypatch.setattr(console.sys, "stderr", proud) + + console.utf8() + print("Počitadlo je dostupné na:", file=console.sys.stdout) + console.sys.stdout.flush() + + assert "Počitadlo je dostupné" in surovy.getvalue().decode("utf-8") + + +def test_proud_bez_reconfigure_se_prezije(monkeypatch): + """Zabalený výstup reconfigure mít nemusí; padat kvůli tomu nesmíme.""" + monkeypatch.setattr(console.sys, "stdout", object()) + monkeypatch.setattr(console.sys, "stderr", object()) + + console.utf8() diff --git a/web.py b/web.py index b2c1d72..57faac7 100644 --- a/web.py +++ b/web.py @@ -18,6 +18,7 @@ from flask import (Flask, abort, g, redirect, render_template, request, url_for) from markupsafe import Markup, escape +import console import net import stats import storage @@ -320,6 +321,8 @@ def hall(): if __name__ == "__main__": + console.utf8() + found = net.addresses() if found: