项目文件夹

文件
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

329 行
13 KiB
Python

from collections.abc import (AsyncIterator, Awaitable, Callable, Mapping,
Sequence)
from dataclasses import dataclass
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import IndexCacheStore
from mirage.cache.read_through import (cache_aware_read_bytes,
cache_aware_read_stream)
from mirage.commands.builtin.grep_helper import ( # yapf: disable
compile_pattern, count_exit_stream, count_records_have_matches,
grep_files_only, grep_lines, grep_recursive, grep_stream, prefix_lines,
resolve_pattern)
from mirage.commands.builtin.utils.lines import split_lines
from mirage.commands.builtin.utils.output import (format_optional_records,
format_records)
from mirage.commands.builtin.utils.stream import _resolve_source
from mirage.commands.builtin.utils.wrap import (call_read_bytes, call_readdir,
call_stat)
from mirage.commands.errors import UsageError
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagView
from mirage.io.stream import exit_on_empty, quiet_match
from mirage.io.types import ByteSource, IOResult
from mirage.types import FileStat, FileType, PathSpec
from mirage.utils.key_prefix import mount_prefix_of
from mirage.utils.path import rebase_raw
@dataclass(frozen=True, slots=True)
class GrepFlags:
"""Parsed grep flags (TS FlagSet parity); the complete set grep honors."""
ignore_case: bool
invert: bool
line_numbers: bool
count_only: bool
files_only: bool
whole_word: bool
fixed_string: bool
only_matching: bool
quiet: bool
recursive: bool
with_filename: bool
no_filename: bool
max_count: int | None
after_context: int
before_context: int
def parse_flags(fl: FlagView, never_match: bool) -> GrepFlags:
"""Convert the raw flag bag into GrepFlags, the only string-keyed reads.
Args:
fl (FlagView): spec-validated view over the raw flag kwargs.
never_match (bool): zero-pattern sentinel from resolve_pattern; it is
a regex, so it suppresses -F.
"""
a_ctx = fl.int("A")
b_ctx = fl.int("B")
c_ctx = fl.int("C")
return GrepFlags(
ignore_case=fl.bool("i"),
invert=fl.bool("v"),
line_numbers=fl.bool("n"),
count_only=fl.bool("c"),
files_only=fl.bool("args_l"),
whole_word=fl.bool("w"),
fixed_string=fl.bool("F") and not never_match,
only_matching=fl.bool("o"),
quiet=fl.bool("q"),
recursive=fl.bool("r") or fl.bool("R"),
with_filename=fl.bool("H"),
no_filename=fl.bool("h"),
max_count=fl.int("m"),
after_context=a_ctx if a_ctx is not None else (c_ctx or 0),
before_context=b_ctx if b_ctx is not None else (c_ctx or 0),
)
async def grep(
paths: list[PathSpec],
texts: Sequence[str] = (),
flags: Mapping[str, object] | None = None,
*,
readdir: Callable[..., Awaitable[list[str]]],
stat: Callable[[PathSpec], Awaitable[FileStat]],
read_bytes: Callable[..., Awaitable[bytes]],
read_stream: Callable[..., AsyncIterator[bytes]] | None,
accessor: Accessor | None = None,
stdin: AsyncIterator[bytes] | bytes | None = None,
index: IndexCacheStore | None = None,
) -> tuple[ByteSource | None, IOResult]:
"""Run grep-style fallback search over backend paths or stdin.
Interprets the raw flag kwargs itself (TS grepGeneric parity), so
backend wrappers only wire paths, texts, flags, and backend I/O.
Args:
paths (list[PathSpec]): Backend paths to search. Empty paths consume
stdin.
texts (Sequence[str]): positional TEXT operands (the pattern unless
-e/-f supplied it).
flags (Mapping[str, object] | None): raw flag kwargs from the
dispatcher (e, f, i, v, n, c, args_l, w, F, o, q, r, R, m,
A, B, C).
readdir (Callable[..., Awaitable[list[str]]]): Directory reader.
stat (Callable[[PathSpec], Awaitable[FileStat]]): Backend stat reader.
read_bytes (Callable[..., Awaitable[bytes]]): Whole-file reader.
read_stream (Callable[..., AsyncIterator[bytes]] | None): Optional
stream reader.
accessor (Accessor | None): Backend accessor passed through wrapper
helpers.
stdin (AsyncIterator[bytes] | bytes | None): Input used when paths is
empty.
index (IndexCacheStore | None): Optional cache index for wrapped
backend calls.
Returns:
tuple[ByteSource | None, IOResult]: Output stream and exit metadata.
"""
read_bytes = cache_aware_read_bytes(read_bytes)
if read_stream is not None:
read_stream = cache_aware_read_stream(read_stream)
fl = FlagView(flags, spec=SPECS["grep"])
pattern, never_match = await resolve_pattern(
texts, fl, read_bytes, accessor, index,
"grep: usage: grep [flags] pattern [path]")
f = parse_flags(fl, never_match)
if paths:
mount_prefix = mount_prefix_of(paths[0].virtual,
paths[0].resource_path)
rd = partial(call_readdir,
readdir,
accessor,
index=index,
prefix=mount_prefix)
st = partial(call_stat,
stat,
accessor,
index=index,
prefix=mount_prefix)
rb = partial(call_read_bytes,
read_bytes,
accessor,
index=index,
prefix=mount_prefix)
if f.files_only:
warnings: list[str] = []
results: list[str] = []
for p in paths:
hits = await grep_files_only(
rd,
st,
rb,
p.virtual,
pattern,
recursive=f.recursive,
ignore_case=f.ignore_case,
invert=f.invert,
line_numbers=f.line_numbers,
count_only=f.count_only,
fixed_string=f.fixed_string,
only_matching=f.only_matching,
max_count=f.max_count,
whole_word=f.whole_word,
warnings=warnings,
read_stream_fn=None,
)
results.extend(rebase_raw(hits, p.virtual, p.raw_path))
stderr = format_optional_records(warnings)
if not results:
return b"", IOResult(exit_code=1, stderr=stderr)
return format_records(results), IOResult(stderr=stderr)
if f.recursive:
pat = compile_pattern(pattern, f.ignore_case, f.fixed_string,
f.whole_word)
# OPTIMIZATION (see #207): this buffers every match into
# all_results and returns it materialized, so
# `grep -r PATTERN dir | head -n 3`
# still scans the whole tree before head sees a line. For plain
# line output (not -c/-l, which must aggregate) this could instead
# yield prefixed matches lazily per file as an async generator
# wrapped in exit_on_empty, letting an early-exiting consumer
# (head, grep -m, grep -q) abort the walk after enough matches.
all_results: list[str] = []
warnings = []
for p in paths:
s = await st(p.virtual)
if s.type == FileType.DIRECTORY:
res = await grep_recursive(
rd,
st,
rb,
p.virtual,
pat,
invert=f.invert,
line_numbers=f.line_numbers,
count_only=f.count_only,
files_only=False,
only_matching=f.only_matching,
max_count=f.max_count,
warnings=warnings,
read_stream_fn=None,
)
all_results.extend(rebase_raw(res, p.virtual, p.raw_path))
else:
data = split_lines(
(await rb(p.virtual)).decode(errors="replace"))
hits = grep_lines(p.raw_path, data, pat, f.invert,
f.line_numbers, f.count_only,
f.files_only, f.only_matching,
f.max_count)
label = "" if f.no_filename else f"{p.raw_path}:"
if f.count_only and hits:
all_results.append(f"{label}{hits[0]}")
else:
all_results.extend(f"{label}{rl}" for rl in hits)
stderr = format_optional_records(warnings)
if not all_results:
return b"", IOResult(exit_code=1, stderr=stderr)
if f.count_only and not count_records_have_matches(all_results):
return format_records(all_results), IOResult(exit_code=1,
stderr=stderr)
return format_records(all_results), IOResult(stderr=stderr)
pat = compile_pattern(pattern, f.ignore_case, f.fixed_string,
f.whole_word)
if len(paths) > 1:
all_results = []
multi_warnings: list[str] = []
for p in paths:
try:
s = await st(p.virtual)
except FileNotFoundError:
multi_warnings.append(
f"grep: {p.raw_path}: No such file or directory")
continue
if s.type == FileType.DIRECTORY:
multi_warnings.append(
f"grep: {p.raw_path}: Is a directory")
continue
data = split_lines((await
rb(p.virtual)).decode(errors="replace"))
hits = grep_lines(p.raw_path, data, pat, f.invert,
f.line_numbers, f.count_only, f.files_only,
f.only_matching, f.max_count)
label = "" if f.no_filename else f"{p.raw_path}:"
if f.count_only:
if hits:
all_results.append(f"{label}{hits[0]}")
elif f.files_only:
all_results.extend(hits)
else:
all_results.extend(f"{label}{r}" for r in hits)
stderr = format_optional_records(multi_warnings)
if not all_results:
return b"", IOResult(exit_code=1, stderr=stderr)
if f.count_only and not count_records_have_matches(all_results):
return format_records(all_results), IOResult(exit_code=1,
stderr=stderr)
return format_records(all_results), IOResult(stderr=stderr)
first_stat = await st(paths[0].virtual)
if first_stat.type == FileType.DIRECTORY:
stderr = f"grep: {paths[0].raw_path}: Is a directory\n".encode()
return b"", IOResult(exit_code=1, stderr=stderr)
if read_stream is not None:
source: AsyncIterator[bytes] = read_stream(accessor, paths[0])
else:
data = await rb(paths[0].virtual)
source = _wrap_bytes(data)
stream = grep_stream(
source,
pat,
invert=f.invert,
line_numbers=f.line_numbers,
only_matching=f.only_matching,
max_count=f.max_count,
count_only=f.count_only,
after_context=f.after_context,
before_context=f.before_context,
)
if f.quiet:
io = IOResult(exit_code=1)
return quiet_match(stream, io), io
io = IOResult()
out = (count_exit_stream(stream, io)
if f.count_only else exit_on_empty(stream, io))
if f.with_filename and not (f.after_context or f.before_context):
# GNU labels context lines with `-` instead of `:`, which the
# uniform prefix cannot reproduce, so -H skips context output.
out = prefix_lines(out, f"{paths[0].raw_path}:")
return out, io
source = _resolve_source(stdin,
"grep: usage: grep [flags] pattern [path]",
error_cls=UsageError)
pat = compile_pattern(pattern, f.ignore_case, f.fixed_string, f.whole_word)
stream = grep_stream(
source,
pat,
invert=f.invert,
line_numbers=f.line_numbers,
only_matching=f.only_matching,
max_count=f.max_count,
count_only=f.count_only,
after_context=f.after_context,
before_context=f.before_context,
)
if f.quiet:
io = IOResult(exit_code=1)
return quiet_match(stream, io), io
io = IOResult()
if f.count_only:
return count_exit_stream(stream, io), io
return exit_on_empty(stream, io), io
async def _wrap_bytes(data: bytes) -> AsyncIterator[bytes]:
yield data
__all__ = ["grep"]