Coverage for src/footman/_encoding.py: 100%
8 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-06 02:59 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-06 02:59 +0000
1"""Byte-order marks — the only encoding hint a file carries about itself.
3footman is UTF-8 end to end. The interfaces it does not control are the
4files other tools wrote: a shell rc that Windows PowerShell 5 saved as
5UTF-16, a config a Windows editor saved as UTF-8 with a leading mark. A
6byte-order mark is the one autodetection that is never a guess, so the
7table lives here once — the completion installer rewrites rc files by it
8(`_shellcomp`), and the config reader accepts or refuses by it
9(`_config`). Two copies would eventually disagree, and a disagreement
10about a file's encoding corrupts the file.
12Stdlib only and import-free of the rest of footman: `_config` is on every
13invocation's path, so this module must cost nothing to import.
14"""
16from __future__ import annotations
18# UTF-8 with a mark is still UTF-8 — the mark is stripped, never refused.
19UTF8_BOM = b"\xef\xbb\xbf"
21# (mark, decode-with, append-with). The append encoder is BOM-free
22# (`utf-8`, `utf-16-le`/`-be`) so writing a tail never injects a second mark.
23BOM_ENCODINGS: tuple[tuple[bytes, str, str], ...] = (
24 (UTF8_BOM, "utf-8-sig", "utf-8"),
25 (b"\xff\xfe", "utf-16", "utf-16-le"),
26 (b"\xfe\xff", "utf-16", "utf-16-be"),
27)
30def sniff_bom(raw: bytes) -> tuple[str, str] | None:
31 """The (decode, append) encodings *raw*'s leading mark implies, or `None`
32 when it carries no byte-order mark at all."""
33 for bom, decode, append in BOM_ENCODINGS:
34 if raw.startswith(bom):
35 return decode, append
36 return None