Files
SimurgArray/src/simurg/formats/detect.py
T
kemalandClaude Opus 5.5 4357d3deef
ci / test (3.12) (push) Failing after 11s
ci / test (3.13) (push) Failing after 12s
P1: file parsers, sample sheet, discovery, CLI inspect
Readers for IDAT v3, BPM (locus entries v4-v8), CSV manifest, EGT
(data blocks 5-9) and GTC v3-v5 (plus a layout-preserving GTC writer);
sample-sheet validation (IN-001..IN-014), IDAT discovery and pairing
(IN-020..IN-022), message catalogue and exit codes (CLI-014/016),
JSON Lines logging (CLI-007/008), atomic writes (CLI-011), run manifest,
and `simurg inspect`.

Validation (docs/validation/P1.md): GTC, BPM and EGT readers agree
exactly with Illumina BeadArrayFiles on GSA v4 / GDA v1 files; GTCs
round-trip byte-identically; BPM and CSV manifests are consistent.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
2026-09-27 16:56:39 +01:00

43 lines
1.2 KiB
Python

"""File-type sniffing for ``simurg inspect`` (spec CLI-020)."""
from __future__ import annotations
import gzip
from pathlib import Path
KINDS = ("idat", "bpm", "egt", "gtc", "csv_manifest", "samplesheet", "vcf", "unknown")
def _head(path: Path, n: int = 4096) -> bytes:
with path.open("rb") as fh:
b = fh.read(n)
if b[:2] == b"\x1f\x8b":
try:
with gzip.open(path, "rb") as gz:
return gz.read(n)
except OSError:
return b
return b
def detect_kind(path: str | Path) -> str:
p = Path(path)
head = _head(p)
name = p.name.lower()
if head[:4] == b"IDAT":
return "idat"
if head[:3] == b"BPM":
return "bpm"
if head[:3] == b"gtc":
return "gtc"
if head.startswith(b"##fileformat=VCF"):
return "vcf"
if name.endswith(".egt") or (len(head) >= 5 and head[:4] == b"\x03\x00\x00\x00"):
return "egt"
text = head.decode("utf-8", errors="replace").lower()
if "[assay]" in text or ("[heading]" in text and "descriptor file name" in text):
return "csv_manifest"
if "sentrixbarcode_a" in text or "beadchipname" in text or "[data]" in text:
return "samplesheet"
return "unknown"