from __future__ import annotations from pathlib import Path from subprocess import Popen, call, check_output, PIPE from zfs_backup.misc import assert_type class CommandInterface: _BINARY: Path @classmethod def _run_command(cls, *args: str) -> None: CommandRunner.run([str(cls._BINARY), *args]) @classmethod def _get_output(cls, *args: str) -> str: return CommandRunner.get_output([str(cls._BINARY), *args]) @classmethod def _open_stream(cls, *args: str) -> Popen: return CommandRunner.send_stream([str(cls._BINARY), *args]) @classmethod def _receive_stream(cls, *args: str, stream: Popen) -> None: CommandRunner.receive_from_stream([str(cls._BINARY), *args], stream=stream) class CommandRunner: @staticmethod def _sanity_check(cmdline: list[str]) -> None: assert_type(cmdline, list, "Command line must be a list") for item in cmdline: assert_type(item, str, "Only strings allowed in command line") @staticmethod def _to_list(command: str | list[str]) -> list[str]: """If the command is a str, return it as list. Otherwise, return the command.""" if isinstance(command, str): return [command] return command @classmethod def run(cls, cmdline: str | list[str]) -> None: """Run the command, wait for it to return.""" cmdline = cls._to_list(cmdline) cls._sanity_check(cmdline) call(cmdline) @classmethod def get_output(cls, cmdline: str | list[str]) -> str: """Run the command, return the output of the command.""" cmdline = cls._to_list(cmdline) cls._sanity_check(cmdline) return check_output(cmdline).strip().decode() @classmethod def receive_from_stream(cls, cmdline: str | list[str], stream: Popen) -> None: """Run the command, receive data from a pipe to stdin.""" cmdline = cls._to_list(cmdline) cls._sanity_check(cmdline) check_output(cmdline, stdin=stream.stdout) @classmethod def send_stream(cls, cmdline: str | list[str]) -> Popen: """Run the command, redirect stdout to a pipe.""" cmdline = cls._to_list(cmdline) cls._sanity_check(cmdline) return Popen(cmdline, stdout=PIPE)