87 lines
1.9 KiB
Python
87 lines
1.9 KiB
Python
import logging
|
|
import os
|
|
import select
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from enum import Enum
|
|
from pathlib import Path
|
|
from typing import IO, Any
|
|
|
|
from .errors import ClanCmdError, CmdOut
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class Log(Enum):
|
|
STDERR = 1
|
|
STDOUT = 2
|
|
BOTH = 3
|
|
|
|
|
|
def handle_output(process: subprocess.Popen, log: Log) -> tuple[str, str]:
|
|
rlist = [process.stdout, process.stderr]
|
|
stdout_buf = b""
|
|
stderr_buf = b""
|
|
|
|
while len(rlist) != 0:
|
|
r, _, _ = select.select(rlist, [], [], 0)
|
|
|
|
def handle_fd(fd: IO[Any] | None) -> bytes:
|
|
if fd and fd in r:
|
|
read = os.read(fd.fileno(), 4096)
|
|
if len(read) != 0:
|
|
return read
|
|
rlist.remove(fd)
|
|
return b""
|
|
|
|
ret = handle_fd(process.stdout)
|
|
if log in [Log.STDOUT, Log.BOTH]:
|
|
sys.stdout.buffer.write(ret)
|
|
|
|
stdout_buf += ret
|
|
ret = handle_fd(process.stderr)
|
|
|
|
if log in [Log.STDERR, Log.BOTH]:
|
|
sys.stderr.buffer.write(ret)
|
|
stderr_buf += ret
|
|
return stdout_buf.decode("utf-8"), stderr_buf.decode("utf-8")
|
|
|
|
|
|
def run(
|
|
cmd: list[str],
|
|
*,
|
|
env: dict[str, str] | None = None,
|
|
cwd: Path = Path.cwd(),
|
|
log: Log = Log.STDERR,
|
|
check: bool = True,
|
|
error_msg: str | None = None,
|
|
) -> CmdOut:
|
|
# Start the subprocess
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
cwd=str(cwd),
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
|
|
stdout_buf, stderr_buf = handle_output(process, log)
|
|
|
|
# Wait for the subprocess to finish
|
|
rc = process.wait()
|
|
cmd_out = CmdOut(
|
|
stdout=stdout_buf,
|
|
stderr=stderr_buf,
|
|
cwd=cwd,
|
|
command=shlex.join(cmd),
|
|
returncode=process.returncode,
|
|
msg=error_msg,
|
|
)
|
|
|
|
if check and rc != 0:
|
|
raise ClanCmdError(cmd_out)
|
|
|
|
return cmd_out
|