diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2026-02-16 12:59:14 +0200 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2026-02-16 12:59:14 +0200 |
| commit | 5f46fde558c467414fa151ad962caaf7f07628fa (patch) | |
| tree | 247d6fd45ddbc811fb71d2009fbbd2042e158791 /jprov/runner.py | |
Initial commit
Diffstat (limited to 'jprov/runner.py')
| -rw-r--r-- | jprov/runner.py | 63 |
1 files changed, 63 insertions, 0 deletions
diff --git a/jprov/runner.py b/jprov/runner.py new file mode 100644 index 0000000..88dfe7f --- /dev/null +++ b/jprov/runner.py @@ -0,0 +1,63 @@ +"""Subprocess runner and logging utilities.""" + +from __future__ import annotations + +import os +import subprocess +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Iterable, Mapping + + +@dataclass +class RunResult: + cmd: list[str] + returncode: int + stdout: str + stderr: str + + +def log_line(level: str, scope: str, message: str) -> None: + ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + print(f"{ts} {level:<5} {scope}: {message}") + + +def run( + cmd: Iterable[str], + *, + log_file: str | None = None, + env: Mapping[str, str] | None = None, + allow_fail: bool = False, + dry_run: bool = False, +) -> RunResult: + """Run a command and optionally tee output to a log file.""" + cmd_list = list(cmd) + if dry_run: + if log_file: + with open(log_file, "a", encoding="utf-8") as fh: + fh.write(f"[dry-run] {' '.join(cmd_list)}\n") + return RunResult(cmd=cmd_list, returncode=0, stdout="", stderr="") + env_map = os.environ.copy() + if env: + env_map.update(env) + + proc = subprocess.Popen( + cmd_list, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + env=env_map, + ) + stdout, stderr = proc.communicate() + + if log_file: + with open(log_file, "a", encoding="utf-8") as fh: + if stdout: + fh.write(stdout) + if stderr: + fh.write(stderr) + + if proc.returncode != 0 and not allow_fail: + raise RuntimeError(f"command failed: {' '.join(cmd_list)} (exit {proc.returncode})") + + return RunResult(cmd=cmd_list, returncode=proc.returncode, stdout=stdout, stderr=stderr) |
