Merge pull request 'cmd: rework redirecting stdout/stderr' (#700) from Mic92-wayland-update into main
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import select
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, NamedTuple
|
from typing import IO, Any, NamedTuple
|
||||||
|
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
|
|
||||||
@@ -17,47 +19,56 @@ class CmdOut(NamedTuple):
|
|||||||
cwd: Path | None = None
|
cwd: Path | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def handle_output(process: subprocess.Popen) -> 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)
|
||||||
|
sys.stdout.buffer.write(ret)
|
||||||
|
stdout_buf += ret
|
||||||
|
ret = handle_fd(process.stderr)
|
||||||
|
sys.stderr.buffer.write(ret)
|
||||||
|
stderr_buf += ret
|
||||||
|
return stdout_buf.decode("utf-8"), stderr_buf.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: list[str], cwd: Path = Path.cwd()) -> CmdOut:
|
def run(cmd: list[str], cwd: Path = Path.cwd()) -> CmdOut:
|
||||||
# Start the subprocess
|
# Start the subprocess
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
cmd, cwd=str(cwd), stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
cmd, cwd=str(cwd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize empty strings for output and error
|
stdout_buf, stderr_buf = handle_output(process)
|
||||||
output = b""
|
|
||||||
error = b""
|
|
||||||
|
|
||||||
# Iterate over the stdout stream
|
|
||||||
for c in iter(lambda: process.stdout.read(1), b""): # type: ignore
|
|
||||||
# Convert bytes to string and append to output
|
|
||||||
output += c
|
|
||||||
# Write to terminal
|
|
||||||
sys.stdout.buffer.write(c)
|
|
||||||
# Iterate over the stderr stream
|
|
||||||
for c in iter(lambda: process.stderr.read(1), b""): # type: ignore
|
|
||||||
# Convert bytes to string and append to error
|
|
||||||
error += c
|
|
||||||
# Write to terminal
|
|
||||||
sys.stderr.buffer.write(c)
|
|
||||||
# Wait for the subprocess to finish
|
# Wait for the subprocess to finish
|
||||||
process.wait()
|
rc = process.wait()
|
||||||
|
|
||||||
output_str = output.decode("utf-8")
|
if rc != 0:
|
||||||
error_str = error.decode("utf-8")
|
|
||||||
|
|
||||||
if process.returncode != 0:
|
|
||||||
raise ClanError(
|
raise ClanError(
|
||||||
f"""
|
f"""
|
||||||
command: {shlex.join(cmd)}
|
command: {shlex.join(cmd)}
|
||||||
working directory: {cwd}
|
working directory: {cwd}
|
||||||
exit code: {process.returncode}
|
exit code: {rc}
|
||||||
stderr:
|
stderr:
|
||||||
{error_str}
|
{stderr_buf}
|
||||||
stdout:
|
stdout:
|
||||||
{output_str}
|
{stdout_buf}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
return CmdOut(output_str, error_str, cwd=cwd)
|
|
||||||
|
return CmdOut(stdout_buf, stderr_buf, cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
def runforcli(func: Callable[..., dict[str, CmdOut]], *args: Any) -> None:
|
def runforcli(func: Callable[..., dict[str, CmdOut]], *args: Any) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user