Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

193 lines
7.5 KiB
Python

# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import dataclasses
import fnmatch
import logging
import posixpath
from collections.abc import Callable
from mirage.accessor.base import Accessor
from mirage.cache.index import IndexCacheStore
from mirage.types import PathSpec
from mirage.utils.key_prefix import rekey
logger = logging.getLogger(__name__)
GLOB_CHARS = ("*", "?", "[")
def has_glob(segment: str) -> bool:
"""Whether a path segment contains shell glob characters.
Args:
segment (str): one path component.
"""
return any(ch in segment for ch in GLOB_CHARS)
def is_word_shaped(p: PathSpec) -> bool:
"""Whether a pattern spec is a typed word (not a directory listing).
A classify-shaped word puts the pattern inside ``virtual``
(``/data/s*/x.txt`` with directory ``/data/s*/``); a dir-shaped spec
(``PathSpec.dir``) sets ``virtual`` to the directory itself.
Args:
p (PathSpec): unresolved pattern spec.
"""
return p.virtual.rstrip("/") != p.directory.rstrip("/")
def spell_match(raw: str, virtual: str, walked: int) -> str:
"""Spell a match the way bash expansion would.
Bash rewrites only the glob segments of the typed word; everything
before the first glob segment keeps its typed spelling, so
``../s*/x.txt`` expands to ``../sub/x.txt``. The walked tail has the
same segment count in the typed word and in the match's virtual
path, so the spelling is the typed head plus the match's last
``walked`` segments.
Args:
raw (str): the pattern word as typed (``PathSpec.raw_path``).
virtual (str): one match's absolute virtual path.
walked (int): segment count from the first glob segment on.
"""
head = raw.rstrip("/").split("/")[:-walked]
tail = virtual.rstrip("/").split("/")[-walked:]
return "/".join([*head, *tail])
async def expand_pattern(
readdir: Callable,
accessor: Accessor,
path: PathSpec,
index: IndexCacheStore | None,
) -> list[PathSpec]:
"""Expand a glob PathSpec segment-by-segment via readdir.
Mirrors bash globbing: every path component containing a glob
character is matched against the entries of its (already expanded)
parent directory, so a mid-path pattern (``pages/Demo_*/page.md``)
never reaches the backend as a literal ``*`` path segment. An
intermediate match that cannot be listed (a file, or a vanished
entry) is skipped, matching bash's directories-only descent for
non-final components.
Args:
readdir (Callable): backend readdir ``(accessor, path, index)``
returning absolute virtual paths.
accessor (Accessor): backend handle passed through to readdir.
path (PathSpec): unresolved spec whose ``resource_path`` still
contains the pattern.
index (IndexCacheStore | None): the per-call cache index.
"""
prefix = path.virtual[:len(path.virtual.rstrip("/")) -
len(path.resource_path)]
segments = path.resource_path.split("/") if path.resource_path else []
# Two spec shapes reach resolvers: a full pattern path (classify), where
# the pattern is already the last segment, and a directory-shaped spec
# (PathSpec.dir), where the pattern applies to the directory's entries.
if path.pattern and (not segments or segments[-1] != path.pattern):
segments = [*segments, path.pattern]
first = next((i for i, seg in enumerate(segments) if has_glob(seg)),
len(segments) - 1)
base = (prefix + "/".join(segments[:first])).rstrip("/") or "/"
level = [base]
for seg in segments[first:]:
next_level: list[str] = []
for parent in level:
spec = PathSpec.from_str_path(
parent, rekey(path.virtual, path.resource_path, parent))
try:
entries = await readdir(accessor, spec, index)
except (FileNotFoundError, NotADirectoryError):
continue
next_level.extend(
e for e in entries
if fnmatch.fnmatch(e.rstrip("/").rsplit("/", 1)[-1], seg))
level = next_level
if not level:
return []
matches = [
PathSpec.from_str_path(e, rekey(path.virtual, path.resource_path, e))
for e in level
]
# A typed word (raw differs from virtual) spells its matches; the
# dir-shaped specs internal expansions build (PathSpec.dir) have no
# typed form and keep the resolved virtual.
if path.raw_path == path.virtual:
return matches
walked = len(segments) - first
return [
dataclasses.replace(m,
raw_path=spell_match(path.raw_path, m.virtual,
walked)) for m in matches
]
async def resolve_glob_with(
readdir: Callable,
accessor: Accessor,
paths: list[PathSpec],
index: IndexCacheStore | None,
cap: int | None = None,
) -> list[PathSpec]:
"""Shared resolve_glob loop over a backend's readdir.
Resolved specs pass through, pattern specs expand segment-by-segment
via :func:`expand_pattern` (mid-path aware, spelled as typed), an
unmatched glob word stays the literal (bash with nullglob off: the
command then errors on it like GNU), and matches cap at ``cap`` when
given. Per-backend glob modules bind their own readdir.
Args:
readdir (Callable): backend readdir ``(accessor, path, index)``
returning absolute virtual paths.
accessor (Accessor): backend handle passed through to readdir.
paths (list[PathSpec]): specs to resolve.
index (IndexCacheStore | None): the per-call cache index.
cap (int | None): cap on matches per pattern before truncation.
"""
result: list[PathSpec] = []
for p in paths:
if isinstance(p, str):
result.append(
PathSpec(virtual=p,
directory=posixpath.dirname(p),
resource_path=p.strip("/")))
continue
if p.resolved:
result.append(p)
elif p.pattern:
matched = await expand_pattern(readdir, accessor, p, index)
if not matched and is_word_shaped(p):
# bash with nullglob off: an unmatched glob word stays
# the literal; the command then errors on it like GNU
# (cat '*.nope' -> No such file or directory, exit 1).
# Dir-shaped specs (PathSpec.dir) are internal
# expansions and keep the empty result.
result.append(
dataclasses.replace(p, pattern=None, resolved=True))
continue
if cap is not None and len(matched) > cap:
logger.warning("%s: %d matches exceeds limit (%d), truncating",
p.directory, len(matched), cap)
matched = matched[:cap]
result.extend(matched)
else:
result.append(p)
return result