from __future__ import annotations import io import tarfile from tarfile import TarFile from typing import TYPE_CHECKING, Callable from ....lib.ExecContext import ExecContext from ....lib.log import DEBUG, log if TYPE_CHECKING: from typing import Iterable from ....lib.FileContext import FileContext def filter( blob: bytes, path_filter: Callable[[str], bool] | None, matched: list[str] | None = None, ) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj = ret, mode = 'w') as tf_out: tf_in = TarFile(fileobj = io.BytesIO(blob)) for info in tf_in.getmembers(): if path_filter is not None and not path_filter(info.name): continue log(DEBUG, f'Adding {info.name}') if matched is not None: matched.append(info.name) buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() def rewrite(blob: bytes, rewrite_filter: Callable[[str], str]) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj = ret, mode = 'w') as tf_out: tf_in = TarFile(fileobj = io.BytesIO(blob)) for info in tf_in.getmembers(): new_name = rewrite_filter(info.name) log(DEBUG, f'Rewriting {info.name} -> {new_name}') info.name = new_name buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() def merge(blobs: Iterable[bytes], overwrite: bool = False) -> bytes: ret = io.BytesIO() with tarfile.open(fileobj = ret, mode = 'w') as tf_out: for blob in blobs: tf_in = TarFile(fileobj = io.BytesIO(blob)) existing_names = tf_out.getnames() for info in tf_in.getmembers(): if not overwrite and info.name in existing_names: continue buf = tf_in.extractfile(info) tf_out.addfile(info, buf) return ret.getvalue() async def extract( dst: FileContext, blob: bytes, root: str | None = None, verbose: bool = False ) -> None: cmd = ['tar'] if root is not None: cmd += ['-C', root] if verbose: cmd += '-v' cmd += ['-x', '-f', '-'] if not isinstance(dst, ExecContext): raise NotImplementedError( 'Extracting tar files to a non-executable ' f'context is not yet implemented: {dst}' ) await dst.run(cmd, verbose = verbose, cmd_input = blob)