Source code for mapstp.utils._io
"""Input/output utility methods."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, TextIO
import os
import sys
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from loguru import logger
from mapstp.utils._re import (
CELL_START_PATTERN,
MCNP_SECTIONS_SEPARATOR_PATTERN,
VOID_CELL_START_PATTERN,
)
if TYPE_CHECKING:
from collections.abc import Iterator
PathLike = str | Path | os.PathLike[Any]
[docs]
def can_override(path: Path, *, override: bool) -> Path:
"""Check if it's allowed to override a `path`.
Args:
path: path, where we are going to write
override: permission to override flag
Returns:
The input `path` to facilitate chaining in mapping in code.
Raises:
FileExistsError: if file exists, but override is not allowed.
"""
if not override and path.exists():
msg = (
f"File {path} already exists."
"Consider to use '--override' command line option or remove the file."
)
raise FileExistsError(msg)
return path
[docs]
def find_first_cell_number(mcnp: str | Path) -> int:
"""Find the first cell number in MCNP model.
Args:
mcnp: an input MCNP model file name
Returns:
the first cell number
Raises:
ValueError: if the cell is not found in the `mcnp` file.
"""
_mcnp = Path(mcnp)
with _mcnp.open(encoding="cp1251") as stream:
for line in stream:
match = CELL_START_PATTERN.search(line)
if match:
return int(match["number"])
msg = f"Cells are not found in {mcnp}. Is it MCNP file?"
raise ValueError(msg)
[docs]
def find_first_void_cell_number(mcnp: str | Path) -> int:
"""Find the first void cell number in MCNP model.
Args:
mcnp: an input MCNP model file name
Returns:
the first void cell number
Raises:
ValueError: if the cell is not found in the `mcnp` file.
"""
_mcnp = Path(mcnp)
with _mcnp.open(encoding="cp1251") as stream:
for line in stream:
match = VOID_CELL_START_PATTERN.search(line)
if match:
return int(match["number"])
msg = f"Void cells are not found in {mcnp}. Is it MCNP file?"
raise ValueError(msg)
[docs]
@contextmanager
def select_output(
output: PathLike | None = None,
*,
override: bool,
) -> Iterator[TextIO]:
"""Select stream for output.
If the `output` is specified, then checks if we can override it.
Args:
output: optional file name for output stream
override: permission to override, if `output` file exists
Yields:
stdout, if `output` file name is not specified (None),
opened stream
"""
if output:
p = Path(output)
can_override(p, override=override)
_output: TextIO = p.open(mode="w", encoding="utf8")
logger.info("Tagged mcnp will be saved to {}", p)
else:
_output = sys.stdout
try:
yield _output
finally:
if _output is not sys.stdout:
_output.close()
[docs]
@dataclass
class MCNPSections:
"""Text sections from an MCNP file."""
cells: str
surfaces: str | None = None
cards: str | None = None
remainder: str | None = None
[docs]
def read_mcnp_sections(mcnp_path: Path) -> MCNPSections:
"""Read text sections from MCNP file.
Args:
mcnp_path: path to file.
Returns:
MCNPSections: - the text sections
"""
sections = MCNP_SECTIONS_SEPARATOR_PATTERN.split(
mcnp_path.read_text(encoding="cp1251"),
maxsplit=3,
)
sections_len = len(sections)
cells = sections[0].strip()
surfaces = sections[1].strip() if sections_len >= 2 else None
cards = sections[2].strip() if sections_len >= 3 else None
if sections_len >= 4:
remainder: str | None = sections[3].strip()
if not remainder:
remainder = None
else:
remainder = None
return MCNPSections(cells, surfaces, cards, remainder)