mirror of
https://github.com/soxoj/maigret.git
synced 2026-09-22 17:54:55 +02:00
Keep the startup banners from crashing on a non-UTF-8 stdout (#3050)
This commit is contained in:
+23
-2
@@ -271,6 +271,27 @@ PATREON_URL = "https://www.patreon.com/soxoj"
|
||||
INTRO_TEXT = "MAIGRET - collect a dossier by username from 3000+ sites"
|
||||
|
||||
|
||||
def _print_encodable(text: str) -> None:
|
||||
"""Print text the active stdout encoding may not be able to represent.
|
||||
|
||||
On Windows, Python takes stdout's encoding from the process ANSI
|
||||
codepage, which is cp1252 on a default install and has no U+2665. The
|
||||
banners below carry one, so printing them raised UnicodeEncodeError
|
||||
before a single site was checked, and --no-color did not help because
|
||||
the character sits in that branch too. PYTHONIOENCODING cannot rescue
|
||||
the PyInstaller build, which ignores PYTHON* environment variables.
|
||||
|
||||
Catching the write rather than reconfiguring the stream is deliberate:
|
||||
colorama's init() replaces sys.stdout with a wrapper that has no
|
||||
reconfigure(), so a stream-level fix would depend on running first.
|
||||
"""
|
||||
try:
|
||||
print(text)
|
||||
except UnicodeEncodeError:
|
||||
encoding = getattr(sys.stdout, "encoding", None) or "ascii"
|
||||
print(text.encode(encoding, errors="replace").decode(encoding, errors="replace"))
|
||||
|
||||
|
||||
def _format_intro(use_color: bool) -> str:
|
||||
if not use_color:
|
||||
return f"[+] {INTRO_TEXT}"
|
||||
@@ -283,7 +304,7 @@ def print_intro_banner(no_color: bool = False, silent: bool = False) -> None:
|
||||
"""Print the Maigret intro tagline. Skipped only in silent (--ai) mode."""
|
||||
if silent:
|
||||
return
|
||||
print(_format_intro(use_color=not no_color))
|
||||
_print_encodable(_format_intro(use_color=not no_color))
|
||||
|
||||
|
||||
def _format_donate_banner(use_color: bool) -> str:
|
||||
@@ -304,4 +325,4 @@ def print_donate_banner(no_color: bool = False, silent: bool = False) -> None:
|
||||
"""Print a colored donation banner. Skipped only in silent (--ai) mode."""
|
||||
if silent:
|
||||
return
|
||||
print(_format_donate_banner(use_color=not no_color))
|
||||
_print_encodable(_format_donate_banner(use_color=not no_color))
|
||||
|
||||
@@ -204,3 +204,52 @@ def test_update_shows_extractor_field_when_verbose(monkeypatch):
|
||||
out = "\n".join(captured)
|
||||
assert "uid: 42" in out
|
||||
assert "_extractor: SomeSchemeAPI" in out
|
||||
|
||||
|
||||
def test_banners_survive_a_stdout_that_cannot_encode_them(tmp_path):
|
||||
"""The banners carry '♥', which the Windows ANSI codepage (cp1252 on a
|
||||
default install) cannot encode, so printing them raised UnicodeEncodeError
|
||||
before a single site was checked. --no-color did not help: the character is
|
||||
in that branch too, and the PyInstaller build ignores PYTHONIOENCODING, so
|
||||
there was no way around it from outside.
|
||||
|
||||
Run the banners in a child process whose stdout really is cp1252 rather
|
||||
than mocking the failure, so this fails on any machine if the guard goes.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
probe = tmp_path / "banner_probe.py"
|
||||
probe.write_text(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
from maigret.notify import print_intro_banner, print_donate_banner
|
||||
|
||||
print_intro_banner(no_color=True)
|
||||
print_donate_banner(no_color=True)
|
||||
print_intro_banner(no_color=False)
|
||||
print_donate_banner(no_color=False)
|
||||
"""
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
env = dict(os.environ)
|
||||
env["PYTHONIOENCODING"] = "cp1252"
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(probe)],
|
||||
capture_output=True,
|
||||
encoding="cp1252",
|
||||
errors="replace",
|
||||
env=env,
|
||||
cwd=str(tmp_path),
|
||||
)
|
||||
|
||||
assert result.returncode == 0, (
|
||||
f"the banner must not crash the run: stderr={result.stderr!r}"
|
||||
)
|
||||
assert "Support Maigret" in result.stdout, (
|
||||
f"the banner must still be printed: stdout={result.stdout!r}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user