§
    ÚŽ’jÇ–  ã                  óN  — d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	m
Z
 ddlmZ ddlmZ ddlmZmZ ddlZdZd	Z ed
d
¬¦  «         G d„ d¦  «        ¦   «         Z ed
d
¬¦  «         G d„ d¦  «        ¦   «         Zdd„Z G d„ de	¦  «        Zdd„Z G d„ de¦  «        ZdS )u†  Backend abstraction for builtin tools.

Provides a :class:`BackendBase` abstract base class that captures the
core I/O primitives shared across all six builtin tools (Bash, Read,
Write, Edit, Grep, Glob).

Every backend implements exactly **three** abstract primitives whose
mechanism genuinely differs per environment:

* :meth:`BackendBase.exec_shell` â€” run a program from an argv list
  (no shell; callers needing shell features wrap with ``sh -c``).
* :meth:`BackendBase.read_file` â€” read raw bytes.
* :meth:`BackendBase.write_file` â€” write raw bytes.

All remaining filesystem operations (``file_exists``, ``is_dir``,
``list_dir``, ``stat_mtime``, ``delete_path``) are derived on the base
class from ``exec_shell`` and work out-of-the-box for any remote
backend.  A backend that has a cheaper native path (e.g.
:class:`LocalBackend` using ``os.*``) simply overrides them.

Concrete implementations:

* :class:`LocalBackend` â€” default; uses ``asyncio`` subprocesses,
  ``aiofiles``, and ``os.*`` for host-local I/O.  Injected automatically
  when no explicit backend is given.
* ``DockerBackend`` â€” uses ``aiodocker`` exec / archive APIs.
* ``E2BBackend`` â€” uses the E2B SDK ``commands`` / ``files`` APIs.

By accepting a ``BackendBase`` parameter, each builtin tool can
operate identically in local, Docker, and E2B workspaces without any
workspace-specific branching inside the tool code itself.
é    )ÚannotationsN)ÚABCÚabstractmethod)Ú	dataclass)Ú
ModuleType)ÚAnyÚAsyncIteratori   z%Y\t%s\t%T@\t%f\0T)ÚfrozenÚslotsc                  ó:   — e Zd ZU dZded<   ded<   ded<   dd	„Zd
S )Ú
ExecResultu@  Result of running a shell command via a backend.

    Attributes:
        exit_code: Process exit code.  ``-1`` conventionally indicates
            an internal failure (timeout, connection error, â€¦).
        stdout: Raw bytes captured from standard output.
        stderr: Raw bytes captured from standard error.
    ÚintÚ	exit_codeÚbytesÚstdoutÚstderrÚreturnÚboolc                ó   — | j         dk    S )z˜Whether the command exited successfully.

        Returns:
            `bool`:
                ``True`` iff the command exited with code ``0``.
        r   )r   ©Úselfs    úOC:\Projects\DSCons\.venv\Lib\site-packages\agentscope\tool\_builtin\_backend.pyÚokzExecResult.okL   s   € ð Œ~ Ò"Ð"ó    N)r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__annotations__r   © r   r   r   r   =   sQ   € € € € € € ðð ð €N€N�NØ€M€M�MØ€M€M�Mð#ð #ð #ð #ð #ð #r   r   c                  óD   — e Zd ZU dZded<   ded<   dZded<   dZd	ed
<   dS )ÚDirEntryuä  One entry from :meth:`BackendBase.scandir`.

    Mirrors :class:`os.DirEntry`: the metadata comes from the same
    directory read as the name, so a listing costs one round trip
    instead of one per entry plus one per attribute.

    Attributes:
        name: The entry's base name, without any leading directory.
        is_dir: Whether the entry is a directory, following symlinks.
        size_bytes: Size in bytes of a file. Always ``None`` for a
            directory â€” its on-disk size says nothing a caller wants â€”
            and ``None`` when it could not be determined (a broken
            symlink, a vanished entry).
        mtime: Modification time as a POSIX timestamp, or ``None``
            for the same reasons.
    ÚstrÚnamer   Úis_dirNz
int | NoneÚ
size_bytesúfloat | NoneÚmtime)r   r   r   r   r   r&   r(   r    r   r   r"   r"   V   sR   € € € € € € ðð ð" €I€I�IØ€L€L�LØ!€JÐ!Ð!Ð!Ñ!Ø€EÐÐÐÑÐÐr   r"   Útextr#   r   c                óV   — |                       dd¦  «                              dd¦  «        S )aS  Normalize Windows/old-Mac line endings to ``\n``.

    Converts ``\r\n`` (Windows) and lone ``\r`` (classic Mac) to a
    single ``\n``.  Builtin tools read files as raw bytes (so binary
    payloads survive intact); when the bytes are decoded as text for
    line-based caching, editing, or matching, the line endings must be
    normalized so that content written on Windows behaves identically to
    content written on POSIX.

    Args:
        text (`str`):
            Decoded file contents.

    Returns:
        `str`:
            The text with all line endings collapsed to ``\n``.
    z
ú
ú)Úreplace)r)   s    r   Ú_normalize_newlinesr.   r   s(   € ð$ �<Š<˜ Ñ%Ô%×-Ò-¨d°DÑ9Ô9Ð9r   c                  ó  — e Zd ZU dZeZded<   d5d„Zd6d	„Zd6d
„Z	d7d„Z
d6d„Zd8d„Zedddœd9d„¦   «         Zed:d„¦   «         Zed;d„¦   «         Zd<d „Zefd=d#„Zd>d$„Zd6d%„Zd7d&„Zd7d'„Zd(d)œd?d+„Zd@d-„ZdAd/„Zd(d0œdBd2„ZdCd3„ZdDd4„ZdS )EÚBackendBaseuñ  Filesystem + subprocess interface consumed by builtin tools.

    Subclasses must implement three abstract primitives â€” ``exec_shell``,
    ``read_file``, and ``write_file`` â€” which are the only operations
    whose mechanism genuinely differs per environment.  The remaining
    filesystem helpers are implemented here on top of ``exec_shell`` and
    work for any backend whose shell is POSIX-like; a backend with a
    cheaper native path may override them (see :class:`LocalBackend`).
    r   Ú_path_moduleÚpathr#   Úpathsr   c                ó(   —  | j         j        |g|¢R Ž S )az  Join one or more path components using the backend's separator.

        Args:
            path (`str`):
                The first path component.
            *paths (`str`):
                Additional components to join onto ``path``.

        Returns:
            `str`:
                The joined path, using the backend environment's path
                separator.
        )r1   Újoin)r   r2   r3   s      r   Ú	join_pathzBackendBase.join_pathµ   s"   € ð &ˆtÔ Ô% dÐ3¨UÐ3Ð3Ð3Ð3r   c                ó6   — | j                              |¦  «        S )aD  Return the directory component of ``path``.

        Args:
            path (`str`):
                A path inside the backend's environment.

        Returns:
            `str`:
                Everything up to (but not including) the last path
                separator. Empty string if ``path`` has no separator.
        )r1   Údirname©r   r2   s     r   r8   zBackendBase.dirnameÅ   s   € ð Ô ×(Ò(¨Ñ.Ô.Ð.r   c                ó6   — | j                              |¦  «        S )a1  Return the final component of ``path``.

        Args:
            path (`str`):
                A path inside the backend's environment.

        Returns:
            `str`:
                Everything after the last path separator. Empty string
                if ``path`` ends with a separator.
        )r1   Úbasenamer9   s     r   r;   zBackendBase.basenameÓ   s   € ð Ô ×)Ò)¨$Ñ/Ô/Ð/r   r   c                ó6   — | j                              |¦  «        S )a:  Return ``True`` if ``path`` is absolute in the backend.

        Args:
            path (`str`):
                A path inside the backend's environment.

        Returns:
            `bool`:
                ``True`` iff ``path`` is absolute under the backend
                environment's path semantics.
        )r1   Úisabsr9   s     r   r=   zBackendBase.isabsá   s   € ð Ô ×&Ò& tÑ,Ô,Ð,r   c                ó6   — | j                              |¦  «        S )u3  Normalize ``path`` (collapse ``..``, ``.``, duplicate seps).

        Pure string operation â€” does not touch the filesystem.

        Args:
            path (`str`):
                A path inside the backend's environment.

        Returns:
            `str`:
                The normalized path.
        )r1   Únormpathr9   s     r   r?   zBackendBase.normpathï   s   € ð Ô ×)Ò)¨$Ñ/Ô/Ð/r   Úcwdc               óÐ   — | j                              |¦  «        r| j                              |¦  «        S | j                              | j                              ||¦  «        ¦  «        S )a>  Return an absolute, normalized version of ``path``.

        Unlike :func:`os.path.abspath`, this helper **never** reads
        the host process's working directory: when ``path`` is
        relative it is joined with the explicitly supplied ``cwd``,
        which must itself be a path that is meaningful inside the
        backend's environment.  This avoids the silent bug where the
        host's ``os.getcwd()`` leaks into paths that will actually be
        used on a remote backend.

        Args:
            path (`str`):
                A path inside the backend's environment.
            cwd (`str`):
                Directory to resolve a relative ``path`` against.
                Ignored when ``path`` is already absolute.

        Returns:
            `str`:
                An absolute, normalized path.
        )r1   r=   r?   r5   )r   r2   r@   s      r   ÚabspathzBackendBase.abspathþ   se   € ð, Ô×"Ò" 4Ñ(Ô(ð 	4ØÔ$×-Ò-¨dÑ3Ô3Ð3ØÔ ×)Ò)ØÔ×"Ò" 3¨Ñ-Ô-ñ
ô 
ð 	
r   N©r@   ÚtimeoutÚcommandú	list[str]ú
str | NonerD   r'   r   c             ƒ  ó
   K  — dS )uÔ  Run a program directly from an argument vector.

        *command* is an executable followed by its arguments â€” it is
        **not** passed through a shell, so callers never have to quote
        or escape arguments and there is no platform-specific quoting
        bug. This makes the primitive portable to Windows, where POSIX
        single-quote escaping (``shlex.quote``) is not understood by
        ``cmd.exe``.

        Callers that genuinely need shell features (pipes, redirects,
        ``&&``) must wrap their command line explicitly, e.g.
        ``["/bin/sh", "-c", command_line]``.

        Args:
            command (`list[str]`):
                Executable path/name followed by its arguments.
            cwd (`str | None`, optional):
                Working directory to run the command in. When ``None``
                the backend's default working directory is used.
            timeout (`float | None`, optional):
                Maximum number of seconds to wait. When ``None`` the
                call waits indefinitely. On timeout the result carries
                an ``exit_code`` of ``-1``.

        Returns:
            `ExecResult`:
                The captured exit code, stdout, and stderr.
        Nr    )r   rE   r@   rD   s       r   Ú
exec_shellzBackendBase.exec_shell  ó
   è è € € € r   r   c              ƒ  ó
   K  — dS )zóRead the full contents of ``path`` as raw bytes.

        Args:
            path (`str`):
                Path to the file inside the backend's environment.

        Returns:
            `bytes`:
                The raw file contents.
        Nr    r9   s     r   Ú	read_filezBackendBase.read_fileA  rJ   r   ÚdataÚNonec              ƒ  ó
   K  — dS )zñWrite ``data`` to ``path``, creating parent directories.

        Args:
            path (`str`):
                Destination path inside the backend's environment.
            data (`bytes`):
                The raw bytes to write.
        Nr    )r   r2   rM   s      r   Ú
write_filezBackendBase.write_fileN  rJ   r   ÚstreamúAsyncIterator[bytes]c              ƒ  óŒ   K  — d„ |2 ¦   «         ƒ d{V —†}|                       |d                     |¦  «        ¦  «        ƒ d{V —† dS )a_  Write a byte stream to ``path``, creating parent directories.

        The default buffers the whole stream and delegates to
        :meth:`write_file`, so peak memory is the payload size; only
        backends that override this (``LocalBackend``,
        ``BubblewrapBackend``, ``K8sBackend``) are constant-memory.
        Callers handling untrusted input must cap the payload
        regardless.

        Args:
            path (`str`):
                Destination path inside the backend's environment.
            stream (`AsyncIterator[bytes]`):
                The chunks to write, in order.
        c              “  ó"   K  — g | 3 d {V —†}|‘Œ
6 S )Nr    )Ú.0Úchunks     r   ú
<listcomp>z,BackendBase.write_stream.<locals>.<listcomp>m  s.   è è € Ð2Ð2Ð2Ð2Ð2Ð2Ð2Ð2 %�%Ð2Ð2Ð2Ð2s   …Nr   )rP   r5   )r   r2   rQ   Úchunkss       r   Úwrite_streamzBackendBase.write_streamY  se   è è € ð( 3Ð2¨6Ð2Ñ2Ô2Ð2Ð2Ð2Ð2Ð2Ð2ˆØ�oŠo˜d C§H¢H¨VÑ$4Ô$4Ñ5Ô5Ð5Ð5Ð5Ð5Ð5Ð5Ð5Ð5Ð5r   Ú
chunk_sizer   c               ó¢   K  — |                       |¦  «        ƒ d{V —†}t          dt          |¦  «        |¦  «        D ]}||||z   …         W V — ŒdS )a¦  Read ``path`` as a byte stream.

        The default reads the whole file through :meth:`read_file` and
        re-slices it, so peak memory is the file size; only backends
        that override this (``LocalBackend``) are constant-memory.
        Overriding requires incremental access to ``exec_shell``
        stdout, which the remote backends do not expose today.

        Args:
            path (`str`):
                Path to the file inside the backend's environment.
            chunk_size (`int`, defaults to 1 MiB):
                The size of each yielded chunk.

        Yields:
            `bytes`:
                Successive chunks of the file, in order.
        Nr   )rL   ÚrangeÚlen)r   r2   rZ   rM   Ústarts        r   Úread_streamzBackendBase.read_streamp  sw   è è € ð. —^’^ DÑ)Ô)Ð)Ð)Ð)Ð)Ð)Ð)ˆÝ˜1�c $™iœi¨Ñ4Ô4ð 	3ð 	3ˆEØ�u˜u zÑ1Ð1Ô2Ð2Ð2Ð2Ð2Ð2ð	3ð 	3r   c              ƒ  óš   K  — |                       dg¦  «        ƒ d{V —†}|j                             dd¬¦  «                             ¦   «         S )u   Return the backend environment's current working directory.

        This is the directory that bare ``exec_shell`` invocations
        (those with ``cwd=None``) execute in.  Tools should call this
        â€” instead of :func:`os.getcwd` â€” whenever they need a default
        path that is meaningful inside the backend, because the host
        process's cwd is meaningless for remote backends
        (Docker / E2B).

        The default implementation runs ``pwd`` via :meth:`exec_shell`,
        which works for any POSIX-like backend.  Backends with cheaper
        native access (e.g. :class:`LocalBackend`, or remote backends
        that already track their workdir) should override it.

        Returns:
            `str`:
                The backend's current working directory.
        ÚpwdNúutf-8r-   ©Úerrors)rI   r   ÚdecodeÚstrip)r   Úresults     r   ÚgetcwdzBackendBase.getcwd�  sT   è è € ð& —’¨ wÑ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/ˆØŒ}×#Ò# G°IÐ#Ñ>Ô>×DÒDÑFÔFÐFr   c              ƒ  ó4  K  — |r|d         dk    r|S t          |¦  «        dk    r|d         d| j        j        fvr|S |                      ddg¦  «        ƒ d{V —†}|j                             dd	¬
¦  «                             ¦   «         }|s|S ||dd…         z   S )u  Expand a leading ``~`` / ``~/`` to the backend's home directory.

        Tools should call this â€” instead of :func:`os.path.expanduser`
        â€” whenever they need to expand ``~`` in a path that lives
        inside the backend's environment, because the host process's
        ``$HOME`` is meaningless for remote backends.

        The default implementation queries ``$HOME`` via
        :meth:`exec_shell` (POSIX-only).  Only the leading ``~`` /
        ``~/foo`` form is expanded; ``~user/...`` is not supported by
        the default and is returned unchanged.  Backends with cheaper
        native access should override (e.g. :class:`LocalBackend`).

        Args:
            path (`str`):
                A path inside the backend's environment, possibly
                starting with ``~``.

        Returns:
            `str`:
                ``path`` with a leading ``~`` / ``~/`` expanded.  If
                ``path`` does not start with ``~``, or starts with
                ``~user`` (unsupported), it is returned unchanged.
        r   Ú~é   ú/ÚprintenvÚHOMENrb   r-   rc   )r]   r1   ÚseprI   r   re   rf   )r   r2   rg   Úhomes       r   Ú
expanduserzBackendBase.expanduser£  s»   è è € ð2 ð 	�t˜A”w #’~�~ØˆKåˆt‰9Œ9�qŠ=ˆ=˜T !œW¨S°$Ô2CÔ2GÐ,HÐHÐHØˆKØ—’¨
°FÐ';Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<ˆØŒ}×#Ò# G°IÐ#Ñ>Ô>×DÒDÑFÔFˆØð 	ØˆKØ�d˜1˜2˜2”h‰Ðr   c              ƒ  ój   K  — |                       dd|g¦  «        ƒ d{V —†}|                     ¦   «         S )a  Return ``True`` if ``path`` exists (file or directory).

        Args:
            path (`str`):
                Path to test inside the backend's environment.

        Returns:
            `bool`:
                ``True`` if the path exists, ``False`` otherwise.
        Útestz-eN©rI   r   ©r   r2   rg   s      r   Úfile_existszBackendBase.file_existsÇ  ó@   è è € ð —’¨°°dÐ';Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<ˆØ�yŠy‰{Œ{Ðr   c              ƒ  ój   K  — |                       dd|g¦  «        ƒ d{V —†}|                     ¦   «         S )a  Return ``True`` if ``path`` is an existing directory.

        Args:
            path (`str`):
                Path to test inside the backend's environment.

        Returns:
            `bool`:
                ``True`` if the path is an existing directory.
        rs   z-dNrt   ru   s      r   r%   zBackendBase.is_dirÕ  rw   r   F©Ú	recursiverz   c             ƒ  óØ   K  — |rd|dddg}n
d|dddddd	g}|                       |¦  «        ƒ d
{V —†}|                     ¦   «         sg S d„ |j                             d¦  «        D ¦   «         S )a‚  List entries under ``path``.

        Output is NUL-delimited (``find -print0`` / ``-printf '%f\0'``)
        and split on ``\0`` so that file names containing spaces or
        newlines are handled correctly.  ``find -printf`` is a GNU
        extension; backends running on non-GNU userlands should override
        this method.

        Args:
            path (`str`):
                Directory to list inside the backend's environment.
            recursive (`bool`, optional):
                When ``True``, return all files underneath ``path`` as
                paths (like ``find path -type f``). When ``False``
                (default), return the immediate children's base names
                (like ``os.listdir``).

        Returns:
            `list[str]`:
                The matched entries, or an empty list if ``path`` does
                not exist or cannot be listed.
        Úfindz-typeÚfz-print0ú	-mindepthÚ1ú	-maxdepthú-printfz%f\0Nc                ó@   — g | ]}|¯|                      d d¬¦  «        ‘ŒS )rb   Úsurrogateescaperc   )re   )rU   Úparts     r   rW   z(BackendBase.list_dir.<locals>.<listcomp>  s?   € ð 
ð 
ð 
àØð
Ø�KŠK˜Ð(9ˆKÑ:Ô:ð
ð 
ð 
r   ó    )rI   r   r   Úsplit)r   r2   rz   rE   rg   s        r   Úlist_dirzBackendBase.list_dirã  s®   è è € ð8 ð 	Ø˜t W¨c°9Ð=ˆGˆGð ØØØØØØØð	ˆGð —’ wÑ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/ˆØ�yŠy‰{Œ{ð 	ØˆIð
ð 
àœ×+Ò+¨EÑ2Ô2ð
ñ 
ô 
ð 	
r   úlist[DirEntry]c           
   ƒ  óV   K  — |                       d|dddddt          g¦  «        ƒ d{V —†S )a…  List one directory level with each entry's metadata.

        The type, size and mtime come back from the same ``find`` run
        as the names, so listing a directory of N entries costs one
        round trip rather than 1 + 3N. Prefer this over
        :meth:`list_dir` plus per-entry calls whenever the metadata is
        wanted; ``find -printf`` is a GNU extension, so backends on
        non-GNU userlands should override it.

        ``%Y`` follows symlinks, matching what :meth:`is_dir` reports;
        the size and mtime describe the link itself, which only
        differs for the link's own few bytes.

        Args:
            path (`str`):
                Directory to list inside the backend's environment.

        Returns:
            `list[DirEntry]`:
                The immediate children, or an empty list if ``path``
                does not exist or cannot be listed.
        r|   r~   r   r€   r�   N©Ú_find_entriesÚ_FIND_ENTRY_FORMATr9   s     r   ÚscandirzBackendBase.scandir  s[   è è € ð. ×'Ò'àØØØØØØÝ"ð	ñ
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ð 	
r   úDirEntry | Nonec              ƒ  ón   K  — |                       d|dddt          gd¬¦  «        ƒ d{V —†}|r|d         ndS )	a›  Return one path's type, size and mtime in a single call.

        The single-path counterpart of :meth:`scandir`, the way
        :func:`os.stat` sits beside :func:`os.scandir`. Prefer it over
        asking :meth:`file_exists`, :meth:`is_dir` and
        :meth:`stat_mtime` in turn, which is three round trips for what
        one command answers.

        Args:
            path (`str`):
                Path to stat inside the backend's environment.

        Returns:
            `DirEntry | None`:
                The entry, or ``None`` if ``path`` does not exist.
                ``name`` is the base name, as :meth:`scandir` reports
                it.
        r|   r€   Ú0r�   T©Úskip_unresolvableNr   rŠ   )r   r2   Úentriess      r   ÚstatzBackendBase.stat9  si   è è € ð& ×*Ò*Ø�T˜;¨¨YÕ8JÐKØ"ð +ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ˆð %Ð.ˆw�qŒzˆz¨$Ð.r   r‘   r’   c          	   ƒ  ó@  K  — |                       |¦  «        ƒ d{V —†}|                     ¦   «         sg S g }|j                             d¦  «        D ]Í}|sŒ|                     dd¬¦  «                             dd¦  «        }t          |¦  «        dk    rŒD|\  }}}	}
	 t          |¦  «        }n# t          $ r d}Y nw xY w	 t          |	¦  «        }n# t          $ r d}Y nw xY w|d	v r|rŒ˜d
\  }}|dk    }| 	                    t          |
||rdn||¬¦  «        ¦  «         ŒÎ|S )u*  Parse the records a ``_FIND_ENTRY_FORMAT`` run prints.

        Args:
            command (`list[str]`):
                The ``find`` argv, already carrying the format.
            skip_unresolvable (`bool`, defaults to ``False``):
                Drop entries whose target could not be followed â€” a
                dangling symlink, most often. :meth:`stat` wants this
                (``os.stat`` follows the link and fails, and a caller
                about to read the file must not be told it is there);
                :meth:`scandir` does not, since ``os.scandir`` lists a
                broken link like any other name.

        Returns:
            `list[DirEntry]`:
                One entry per record, or an empty list when ``find``
                failed (a missing path, most often).
        Nr…   rb   rƒ   rc   ú	é   é   )ÚNÚLÚ?)NNÚd©r$   r%   r&   r(   )rI   r   r   r†   re   r]   r   Ú
ValueErrorÚfloatÚappendr"   )r   rE   r’   rg   r“   ÚrecordÚfieldsÚkindÚraw_sizeÚ	raw_mtimer$   Úsizer(   r%   s                 r   r‹   zBackendBase._find_entriesR  s   è è € ð0 —’ wÑ/Ô/Ð/Ð/Ð/Ð/Ð/Ð/ˆØ�yŠy‰{Œ{ð 	ØˆIà"$ˆØ”m×)Ò)¨%Ñ0Ô0ð "	ð "	ˆFØð ØØ—]’] 7Ð3D�]ÑEÔE×KÒKØØñô ˆFõ �6‰{Œ{˜aÒÐØØ.4Ñ+ˆD�(˜I tðÝ#& x¡=¤=��øÝð ð ð Ø���ðøøøðÝ&+¨IÑ&6Ô&6��øÝð ð ð Ø���ðøøøð �Ð&Ð&Ø$ð ØØ(‘��eØ˜S’[ˆFØ�NŠNÝØØ!Ø'-Ð7˜t˜t°4Øð	ñ ô ñô ð ð ð ˆs$   ÂB,Â,B;Â:B;Â?CÃCÃCc              ƒ  óB  K  — t          j        |¦  «        }d|› d|› d�}|                      dd|g¦  «        ƒ d{V —†}|                     ¦   «         sdS 	 t	          |j                             dd¬	¦  «                             ¦   «         ¦  «        S # t          $ r Y dS w xY w)
aª  Return the modification time of ``path``, or ``None``.

        Tries GNU ``stat -c %Y`` first and falls back to BSD
        ``stat -f %m`` so the same call works across coreutils and
        BSD/macOS userlands.  The two attempts are combined with ``||``,
        so this default wraps a ``sh -c`` script; backends without a
        POSIX shell (e.g. :class:`LocalBackend`) override it.

        Args:
            path (`str`):
                Path to stat inside the backend's environment.

        Returns:
            `float | None`:
                The modification time as a POSIX timestamp, or ``None``
                if the path does not exist or cannot be stat'd.
        zstat -c %Y z 2>/dev/null || stat -f %m z 2>/dev/nullÚshz-cNrb   r-   rc   )	ÚshlexÚquoterI   r   rŸ   r   re   rf   rž   )r   r2   ÚquotedÚscriptrg   s        r   Ú
stat_mtimezBackendBase.stat_mtime”  sß   è è € õ$ ”˜TÑ"Ô"ˆð/˜&ð /ð /Ø ð/ð /ð /ð 	ð —’¨¨d°FÐ';Ñ<Ô<Ð<Ð<Ð<Ð<Ð<Ð<ˆØ�yŠy‰{Œ{ð 	Ø�4ð	ÝØ”×$Ò$ W°YÐ$Ñ?Ô?×EÒEÑGÔGñô ð øõ ð 	ð 	ð 	Ø�4�4ð	øøøs   Á:B Â
BÂBc              ƒ  óF   K  — |                       dd|g¦  «        ƒ d{V —† dS )a)  Delete ``path`` (file or directory tree).

        If ``path`` does not exist the call is a silent no-op (like
        ``rm -rf``).  Handles both files and directories (recursively).

        Args:
            path (`str`):
                Path to delete inside the backend's environment.
        Úrmz-rfN)rI   r9   s     r   Údelete_pathzBackendBase.delete_pathµ  s9   è è € ð �oŠo˜t U¨DÐ1Ñ2Ô2Ð2Ð2Ð2Ð2Ð2Ð2Ð2Ð2Ð2r   )r2   r#   r3   r#   r   r#   ©r2   r#   r   r#   ©r2   r#   r   r   )r2   r#   r@   r#   r   r#   ©rE   rF   r@   rG   rD   r'   r   r   ©r2   r#   r   r   ©r2   r#   rM   r   r   rN   ©r2   r#   rQ   rR   r   rN   ©r2   r#   rZ   r   r   rR   ©r   r#   ©r2   r#   rz   r   r   rF   ©r2   r#   r   rˆ   ©r2   r#   r   rŽ   )rE   rF   r’   r   r   rˆ   ©r2   r#   r   r'   ©r2   r#   r   rN   )r   r   r   r   Ú	posixpathr1   r   r6   r8   r;   r=   r?   rB   r   rI   rL   rP   rY   ÚDEFAULT_READ_CHUNK_SIZEr_   rh   rq   rv   r%   r‡   r�   r”   r‹   r­   r°   r    r   r   r0   r0   Š   sF  € € € € € € ðð ðL  )€LÐ(Ð(Ð(Ñ(ð4ð 4ð 4ð 4ð /ð /ð /ð /ð0ð 0ð 0ð 0ð-ð -ð -ð -ð0ð 0ð 0ð 0ð
ð 
ð 
ð 
ð< ð
 Ø $ð"ð "ð "ð "ð "ñ „^ð"ðH ð
ð 
ð 
ñ „^ð
ð ðð ð ñ „^ðð6ð 6ð 6ð 6ð4 2ð3ð 3ð 3ð 3ð 3ð:Gð Gð Gð Gð,"ð "ð "ð "ðHð ð ð ðð ð ð ð$  ð	0
ð 0
ð 0
ð 0
ð 0
ð 0
ðd"
ð "
ð "
ð "
ðH/ð /ð /ð /ð: #(ð	@ð @ð @ð @ð @ð @ðDð ð ð ðB
3ð 
3ð 
3ð 
3ð 
3ð 
3r   r0   údict[str, Any]c                 óT   — t           j        dk    ri S ddl} dt          | dd¦  «        iS )a  Return platform-specific subprocess creation options.

    Returns:
        `dict[str, Any]`:
            Extra keyword arguments for ``create_subprocess_shell``.
            Empty on POSIX; on Windows it sets ``creationflags`` to
            suppress a console window.
    Úntr   NÚcreationflagsÚCREATE_NO_WINDOWi   )Úosr$   Ú
subprocessÚgetattr)rÆ   s    r   Ú_subprocess_creation_kwargsrÈ   Å  sD   € õ 
„w�$‚€Øˆ	àÐÐÐð 	�ØØØñ
ô 
ðð r   c                  ó¢   — e Zd ZdZej        Zdddœd)d„Zd*d„Zd+d„Z	d,d„Z
efd-d„Zd.d„Zd/d„Zd0d„Zd0d„Zdd œd1d"„Zd2d$„Zd3d&„Zd4d'„Zd5d(„ZdS )6ÚLocalBackenduh  Host-local :class:`BackendBase` implementation.

    Uses ``asyncio.create_subprocess_exec``, ``aiofiles``, and the
    ``os`` module.  This is the default backend injected when no
    explicit one is given to a builtin tool.  Commands are spawned
    directly from their argument vector (no shell), which avoids the
    POSIX-vs-``cmd.exe`` quoting mismatch and makes the backend work on
    Windows.  The derived filesystem helpers are overridden with native
    ``os.*`` calls â€” faster and more robust than shelling out, and
    portable to Windows where ``test`` / ``find`` / ``stat`` are
    unavailable.
    NrC   rE   rF   r@   rG   rD   r'   r   r   c             ƒ  ó’  K  — t          ¦   «         }|�||d<   	 t          j        |t          j        j        t          j        j        dœ|¤Žƒ d{V —†}nV# t
          t          t          f$ r<}t          ddt          |¦  «         
                    d¦  «        ¬¦  «        cY d}~S d}~ww xY w	 t          j        |                     ¦   «         |¬¦  «        ƒ d{V —†\  }}nU# t          j        $ rC |                     ¦   «          |                     ¦   «         ƒ d{V —† t          d	dd
¬¦  «        cY S w xY wt          |j        pd||¬¦  «        S )a  Run a program via ``asyncio.create_subprocess_exec``.

        The program is spawned directly from *command* without an
        intervening shell, so no argument quoting is required and the
        same code path works on POSIX and Windows.

        Args:
            command (`list[str]`):
                Executable path/name followed by its arguments.
            cwd (`str | None`, optional):
                Working directory for the subprocess. When ``None`` the
                current process working directory is used.
            timeout (`float | None`, optional):
                Maximum number of seconds to wait before the process is
                killed and an ``exit_code`` of ``-1`` is returned.

        Returns:
            `ExecResult`:
                The captured exit code, stdout, and stderr. If the
                executable cannot be found or spawned, ``exit_code`` is
                ``127`` (matching a shell's "command not found"), with
                the OS error message on stderr.
        Nr@   )r   r   é   r   rb   )r   r   r   )rD   éÿÿÿÿs	   timed outr   )rÈ   ÚasyncioÚcreate_subprocess_execrÆ   ÚPIPEÚFileNotFoundErrorÚNotADirectoryErrorÚOSErrorr   r#   ÚencodeÚwait_forÚcommunicateÚTimeoutErrorÚkillÚ
returncode)	r   rE   r@   rD   ÚkwargsÚprocessÚexcr   r   s	            r   rI   zLocalBackend.exec_shellï  sß  è è € õ< -Ñ.Ô.ˆØˆ?ØˆF�5‰Mð	Ý#Ô:ØÝÔ)Ô.ÝÔ)Ô.ðð ð ð	ð ð ð ð ð ð ð ˆGˆGøõ "Õ#5µwÐ?ð 		ð 		ð 		õ
 ØØÝ˜3‘x”x—’ wÑ/Ô/ðñ ô ð ð ð ð ð ð øøøøð		øøøð	MÝ#*Ô#3Ø×#Ò#Ñ%Ô%Øð$ñ $ô $ð ð ð ð ð ð ‰NˆF�F�Føõ Ô#ð 	Mð 	Mð 	MØ�LŠL‰NŒNˆNØ×%Ò%Ñ'Ô'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ý¨°3¸|ÐLÑLÔLÐLÐLÐLð	Møøøõ
 ØÔ(Ð-¨AØØð
ñ 
ô 
ð 	
s0   ™8A ÁB%Á)1B ÂB%Â B%Â)1C ÃAD-Ä,D-r2   r#   r   c              ƒ  ó¾   K  — t          j        |d¬¦  «        4 ƒd{V —†}|                     ¦   «         ƒ d{V —†cddd¦  «        ƒd{V —† S # 1 ƒd{V —†swxY w Y   dS )zÇRead a local file as raw bytes.

        Args:
            path (`str`):
                Path to the local file.

        Returns:
            `bytes`:
                The raw file contents.
        Úrb©ÚmodeN©ÚaiofilesÚopenÚread)r   r2   r}   s      r   rL   zLocalBackend.read_file3  sï   è è € õ ”= ¨DÐ1Ñ1Ô1ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"°QØŸš™œ�>�>�>�>�>�>ð	"ð 	"ð 	"ð 	"ñ 	"ô 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"ð 	"øøøð 	"ð 	"ð 	"ð 	"ð 	"ð 	"s   ŸAÁ
AÁArM   rN   c              ƒ  ó0  K  — t           j                             |¦  «        }|rt          j        |d¬¦  «         t	          j        |d¬¦  «        4 ƒd{V —†}|                     |¦  «        ƒ d{V —† ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )zãWrite *data* to a local file, creating parent dirs.

        Args:
            path (`str`):
                Destination path on the local filesystem.
            data (`bytes`):
                The raw bytes to write.
        T©Úexist_okÚwbrß   N©rÅ   r2   r8   Úmakedirsrâ   rã   Úwrite)r   r2   rM   Úparentr}   s        r   rP   zLocalBackend.write_fileA  s*  è è € õ ”—’ Ñ&Ô&ˆØð 	/ÝŒK˜¨Ð.Ñ.Ô.Ð.Ý”= ¨DÐ1Ñ1Ô1ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 °QØ—'’'˜$‘-”-ÐÐÐÐÐÐÐð	 ð 	 ð 	 ñ 	 ô 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 ð 	 øøøð 	 ð 	 ð 	 ð 	 ð 	 ð 	 s   ÁBÂ
BÂBrQ   rR   c              ƒ  óH  K  — t           j                             |¦  «        }|rt          j        |d¬¦  «         t	          j        |d¬¦  «        4 ƒd{V —†}|2 3 d{V —†}|                     |¦  «        ƒ d{V —† Œ#6 	 ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )züWrite a byte stream to a local file, chunk by chunk.

        Args:
            path (`str`):
                Destination path on the local filesystem.
            stream (`AsyncIterator[bytes]`):
                The chunks to write, in order.
        Træ   rè   rß   Nré   )r   r2   rQ   rì   r}   rV   s         r   rY   zLocalBackend.write_streamP  s\  è è € õ ”—’ Ñ&Ô&ˆØð 	/ÝŒK˜¨Ð.Ñ.Ô.Ð.Ý”= ¨DÐ1Ñ1Ô1ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%°QØ%ð %ð %ð %ð %ð %ð %ð %�eØ—g’g˜e‘n”nÐ$Ð$Ð$Ð$Ð$Ð$Ð$Ð$ð  &˜vð	%ð 	%ð 	%ñ 	%ô 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%ð 	%øøøð 	%ð 	%ð 	%ð 	%ð 	%ð 	%s   ÁBÁA<ÁBÂ
BÂBrZ   r   c               ó  K  — t          j        |d¬¦  «        4 ƒd{V —†}|                     |¦  «        ƒ d{V —†x}r"|W V — |                     |¦  «        ƒ d{V —†x}°"	 ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )aU  Read a local file chunk by chunk, never holding it whole.

        Args:
            path (`str`):
                Path to the local file.
            chunk_size (`int`, defaults to 1 MiB):
                The size of each yielded chunk.

        Yields:
            `bytes`:
                Successive chunks of the file, in order.
        rÞ   rß   Nrá   )r   r2   rZ   r}   rV   s        r   r_   zLocalBackend.read_streamd  s@  è è € õ" ”= ¨DÐ1Ñ1Ô1ð 	ð 	ð 	ð 	ð 	ð 	ð 	°QØ!"§¢¨
Ñ!3Ô!3Ð3Ð3Ð3Ð3Ð3Ð3Ð3�%ð Ø����ð "#§¢¨
Ñ!3Ô!3Ð3Ð3Ð3Ð3Ð3Ð3Ð3�%ð ð ð	ð 	ð 	ñ 	ô 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	ð 	øøøð 	ð 	ð 	ð 	ð 	ð 	s   ŸA A3Á3
A=Â A=c              ƒ  ó,   K  — t          j        ¦   «         S )u¬   Return the host process's current working directory.

        Returns:
            `str`:
                ``os.getcwd()`` â€” avoids spawning a ``pwd`` subprocess.
        )rÅ   rh   r   s    r   rh   zLocalBackend.getcwdy  s   è è € õ Œy‰{Œ{Ðr   c              ƒ  óD   K  — t           j                             |¦  «        S )u"  Expand ``~`` using the host process's ``$HOME``.

        Args:
            path (`str`):
                A local path, possibly starting with ``~``.

        Returns:
            `str`:
                ``os.path.expanduser(path)`` â€” avoids spawning a
                subprocess.
        )rÅ   r2   rq   r9   s     r   rq   zLocalBackend.expanduser‚  s   è è € õ Œw×!Ò! $Ñ'Ô'Ð'r   r   c              ƒ  óD   K  — t           j                             |¦  «        S )zÀCheck if a local path exists.

        Args:
            path (`str`):
                Path to test.

        Returns:
            `bool`:
                ``True`` if the path exists.
        )rÅ   r2   Úexistsr9   s     r   rv   zLocalBackend.file_exists�  s   è è € õ Œw�~Š~˜dÑ#Ô#Ð#r   c              ƒ  óD   K  — t           j                             |¦  «        S )zÚCheck if a local path is a directory.

        Args:
            path (`str`):
                Path to test.

        Returns:
            `bool`:
                ``True`` if the path is an existing directory.
        )rÅ   r2   Úisdirr9   s     r   r%   zLocalBackend.is_dir�  s   è è € õ Œw�}Š}˜TÑ"Ô"Ð"r   Fry   rz   c             ƒ  óà   K  — |rWg }t          j        |¦  «        D ]>\  }}}|D ]5}|                     t           j                             ||¦  «        ¦  «         Œ6Œ?|S t          j        |¦  «        S )aõ  List local directory entries.

        Mirrors the base contract using native ``os`` calls.

        Args:
            path (`str`):
                Directory to list.
            recursive (`bool`, optional):
                When ``True``, return file paths underneath ``path``
                (``os.walk``). When ``False`` (default), return the
                immediate children's base names (``os.listdir``).

        Returns:
            `list[str]`:
                The matched entries.
        )rÅ   Úwalkr    r2   r5   Úlistdir)r   r2   rz   ÚresultsÚrootÚ_dirsÚfilesr}   s           r   r‡   zLocalBackend.list_dirª  s�   è è € ð, ð 	Ø!#ˆGÝ&(¤g¨d¡m¤mð :ð :Ñ"��e˜UØð :ð :�AØ—N’N¥2¤7§<¢<°°aÑ#8Ô#8Ñ9Ô9Ð9Ð9ð:àˆNÝŒz˜$ÑÔÐr   rˆ   c           
   ƒ  ó”  K  — g }	 t          j        |¦  «        5 }|D ]}	 |                     ¦   «         }|                     ¦   «         }|j        |j        }}n# t          $ r	 d\  }}}Y nw xY w|                     t          |j	        ||rdn||¬¦  «        ¦  «         Œ€	 ddd¦  «         n# 1 swxY w Y   n# t          $ r g cY S w xY w|S )u
  List a local directory with each entry's metadata.

        ``os.scandir`` carries the type in the directory record itself,
        so this is cheaper than ``os.listdir`` followed by a ``stat``
        per entry â€” the same reason the base class batches its ``find``.

        Args:
            path (`str`):
                Directory to list.

        Returns:
            `list[DirEntry]`:
                The immediate children, or an empty list if ``path``
                does not exist or cannot be listed.
        )FNNNr�   )
rÅ   r�   r%   r”   Úst_sizeÚst_mtimerÓ   r    r"   r$   )	r   r2   r“   ÚitÚentryr%   r”   r¦   r(   s	            r   r�   zLocalBackend.scandirÈ  sV  è è € ð  #%ˆð	Ý”˜DÑ!Ô!ð  RØð ð �Eð@Ø!&§¢¡¤˜Ø$Ÿzšz™|œ|˜Ø&*¤l°D´M˜e˜˜øÝ"ð @ð @ð @Ø.?Ñ+˜  e e eð@øøøà—N’NÝ Ø!&¤Ø#)Ø/5Ð'? t t¸4Ø"'ð	ñ ô ñô ð ð ððð ð ñ ô ð ð ð ð ð ð øøøð ð ð ð øøõ$ ð 	ð 	ð 	ØˆIˆIˆIð	øøøàˆsW   †B6 šB* 6AÁB*ÁA*Á'B*Á)A*Á*3B*ÂB6 Â*B.Â.B6 Â1B.Â2B6 Â6CÃCrŽ   c              ƒ  ó  K  — 	 t          j        |¦  «        }n# t          $ r Y dS w xY wt           j                             |¦  «        }t          t           j                             |¦  «        ||rdn|j        |j        ¬¦  «        S )a  Return one local path's type, size and mtime.

        Args:
            path (`str`):
                Path to stat.

        Returns:
            `DirEntry | None`:
                The entry, or ``None`` if ``path`` does not exist or
                cannot be stat'd.
        Nr�   )	rÅ   r”   rÓ   r2   rô   r"   r;   rý   rþ   )r   r2   Úinfor%   s       r   r”   zLocalBackend.statð  s�   è è € ð	å”7˜4‘=”=ˆDˆDøÝð 	ð 	ð 	Ø�4�4ð	øøøå”—’˜tÑ$Ô$ˆÝÝ”×!Ò! $Ñ'Ô'ØØ%Ð7�t�t¨4¬<Ø”-ð	
ñ 
ô 
ð 	
s   „ ™
'¦'c              ƒ  ój   K  — 	 t          j        |¦  «        j        S # t          t          f$ r Y dS w xY w)a3  Return the modification time of a local file.

        Args:
            path (`str`):
                Path to stat.

        Returns:
            `float | None`:
                The modification time as a POSIX timestamp, or ``None``
                if the path does not exist or cannot be stat'd.
        N)rÅ   r”   rþ   rÓ   rÑ   r9   s     r   r­   zLocalBackend.stat_mtime	  sD   è è € ð	Ý”7˜4‘=”=Ô)Ð)øÝÕ*Ð+ð 	ð 	ð 	Ø�4�4ð	øøøs   „ �2±2c              ƒ  óÞ   K  — t           j                             |¦  «        sdS t           j                             |¦  «        rt	          j        |¦  «         dS t          j        |¦  «         dS )z¡Delete a local file or directory tree.

        No-op if *path* does not exist.

        Args:
            path (`str`):
                Path to delete.
        N)rÅ   r2   rò   rô   ÚshutilÚrmtreeÚremover9   s     r   r°   zLocalBackend.delete_path  s`   è è € õ Œw�~Š~˜dÑ#Ô#ð 	ØˆFÝŒ7�=Š=˜ÑÔð 	ÝŒM˜$ÑÔÐÐÐåŒI�d‰OŒOˆOˆOˆOr   r³   r´   rµ   r¶   r·   r¸   r±   r²   r¹   rº   r»   r¼   r½   )r   r   r   r   rÅ   r2   r1   rI   rL   rP   rY   r¿   r_   rh   rq   rv   r%   r‡   r�   r”   r­   r°   r    r   r   rÊ   rÊ   Ü  sr  € € € € € ðð ð  ”7€Lð Ø $ðB
ð B
ð B
ð B
ð B
ð B
ðH"ð "ð "ð "ð ð  ð  ð  ð%ð %ð %ð %ð. 2ðð ð ð ð ð*ð ð ð ð(ð (ð (ð (ð$ð $ð $ð $ð#ð #ð #ð #ð"  ð	 ð  ð  ð  ð  ð  ð<&ð &ð &ð &ðP
ð 
ð 
ð 
ð2ð ð ð ð"ð ð ð ð ð r   rÊ   )r)   r#   r   r#   )r   rÀ   )r   Ú
__future__r   rÎ   rÅ   r¾   r©   r  Úabcr   r   Údataclassesr   Útypesr   Útypingr   r	   râ   r¿   rŒ   r   r"   r.   r0   rÈ   rÊ   r    r   r   ú<module>r     sâ  ððð ðB #Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø Ð Ð Ð Ø €€€Ø €€€Ø #Ð #Ð #Ð #Ð #Ð #Ð #Ð #Ø !Ð !Ð !Ð !Ð !Ð !Ø Ð Ð Ð Ð Ð Ø %Ð %Ð %Ð %Ð %Ð %Ð %Ð %à €€€ð &Ð ð
 -Ð ð
 €�$˜dÐ#Ñ#Ô#ð#ð #ð #ð #ð #ñ #ô #ñ $Ô#ð#ð0 €�$˜dÐ#Ñ#Ô#ðð ð ð ð ñ ô ñ $Ô#ðð6:ð :ð :ð :ð0u3ð u3ð u3ð u3ð u3�#ñ u3ô u3ð u3ðvð ð ð ð.Lð Lð Lð Lð L�;ñ Lô Lð Lð Lð Lr   