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

1"""Byte-order marks — the only encoding hint a file carries about itself. 

2 

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. 

11 

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""" 

15 

16from __future__ import annotations 

17 

18# UTF-8 with a mark is still UTF-8 — the mark is stripped, never refused. 

19UTF8_BOM = b"\xef\xbb\xbf" 

20 

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) 

28 

29 

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