Source code for pycsamt.seg.sections
# Author: LKouadio <etanoyau@gmail.com>
# License: LGPL-3.0
from __future__ import annotations
import os
from collections.abc import Iterator
from ..exceptions import EdIDataError
from ..log.logger import get_logger
from .base import EDIComponentBase
from .mtemap import MTEMAP
from .other import OtherSECT
from .spectra import SpectraSECT
from .time_series import TSect
from .validation import _extract_tag
logger = get_logger(__name__)
__all__ = ["SECT_REGISTRY", "iter_sections"]
# Tag -> header loader
SECT_REGISTRY: dict[str, type[EDIComponentBase]] = {
">=MTSECT": MTEMAP,
">=EMAPSECT": MTEMAP,
">=SPECTRASECT": SpectraSECT,
">=TSERIESSECT": TSect,
">=OTHERSECT": OtherSECT,
}
def _find_header_end(lines: list[str], start: int) -> int:
"""
Return index of first line after header. Stop at next line
that starts with '>' (either '>BLOCK' or '>=' new section).
"""
j = start + 1
n = len(lines)
while j < n:
s = lines[j].lstrip()
if s.startswith(">"):
break
j += 1
return j
def _make_header(
tag: str,
header_lines: list[str],
body_start: int,
) -> EDIComponentBase:
"""
Create a header instance for tag and feed it the raw lines.
If the class exposes a .read, call it; then inject
.start_data_lines_num for downstream IO.
"""
cls = SECT_REGISTRY[tag]
inst = cls() # type: ignore[call-arg]
# Be liberal: if header exposes .read(lines), use it.
read = getattr(inst, "read", None)
if callable(read):
try:
read(header_lines)
except TypeError:
# Some headers may not accept args; skip.
read()
# Attach where body starts for IO loaders.
try:
inst.start_data_lines_num = body_start
except Exception:
pass
return inst
[docs]
def iter_sections(
edi_path: str,
*,
include: list[str] | None = None,
) -> Iterator[tuple[str, EDIComponentBase, int]]:
"""
Yield (tag, header_obj, body_start_line) for each '>=...' block.
Parameters
----------
edi_path : str
Path to EDI file.
include : list of str, optional
If given, only yield tags in this list (case-insens.).
Tags must match keys in SECT_REGISTRY.
Yields
------
(tag, header, body_start) : tuple
tag : str, e.g. ">=MTSECT"
header : parsed header instance
body_start : int, index of first line after header,
i.e., where the first '>BLOCK' (or next section)
begins.
"""
if not os.path.isfile(edi_path):
raise FileNotFoundError(f"{edi_path!r} is not a file.")
with open(edi_path, encoding="utf-8") as f:
lines = f.readlines()
want = {t.upper(): True for t in include} if include else None
i = 0
n = len(lines)
while i < n:
ln = lines[i]
tag = _extract_tag(ln)
if not tag or not tag.startswith(">="):
i += 1
continue
tag_u = tag.upper()
if tag_u not in SECT_REGISTRY:
# Unknown top-level block: skip to next tag.
i += 1
continue
if want is not None and tag_u not in want:
i += 1
continue
j = _find_header_end(lines, i)
header_lines = [s.rstrip("\n") for s in lines[i + 1 : j]]
try:
header = _make_header(tag_u, header_lines, j)
except Exception as exc:
raise EdIDataError(
f"Failed to parse header {tag_u} at line {i}: {exc}"
) from exc
yield (tag_u, header, j)
i = j