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

677 lines
21 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 re
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.constants import PatternType
from mirage.commands.builtin.grep_context import grep_context_lines
from mirage.commands.builtin.utils.types import (_AsyncReadBytes,
_AsyncReaddir, _AsyncStat)
from mirage.commands.builtin.utils.wrap import call_read_bytes
from mirage.commands.errors import UsageError
from mirage.commands.resolve import COMPOUND_EXTENSIONS
from mirage.commands.spec.types import FlagView
from mirage.io.async_line_iterator import AsyncLineIterator
from mirage.io.types import IOResult
from mirage.types import FileType, PathSpec
from mirage.utils.key_prefix import mount_prefix_of
BINARY_EXTENSIONS = frozenset({
".parquet",
".orc",
".feather",
".arrow",
".ipc",
".hdf5",
".h5",
})
NEVER_MATCH = r"(?!)"
def classify_pattern(
pattern: str,
fixed_string: bool,
) -> PatternType:
"""Classify a grep pattern for API push-down decisions.
Args:
pattern (str): the search pattern.
fixed_string (bool): True if -F flag is set.
Returns:
PatternType: EXACT, SIMPLE, or REGEX.
"""
if "\n" in pattern:
return PatternType.REGEX
if fixed_string:
return PatternType.EXACT
if re.fullmatch(r'[\w\s\-_.]+', pattern):
return PatternType.SIMPLE
return PatternType.REGEX
_REGEX_BREAKERS = frozenset(".^$*+?()|{}")
_MIN_SEARCH_LITERAL = 3
def extract_required_literal(pattern: str) -> str | None:
"""Longest substring every match of a regex must contain.
Returns a literal that any line matching ``pattern`` is guaranteed to
contain, suitable for narrowing via a literal search API before the real
regex is scanned locally. Conservative: returns None whenever a required
literal cannot be proven (top-level alternation, character classes,
escapes, runs shorter than ``_MIN_SEARCH_LITERAL``), so the caller falls
back to a full scan rather than risk a false negative.
Args:
pattern (str): a regular expression.
Returns:
str | None: the longest required literal, or None.
"""
if "|" in pattern:
return None
runs: list[str] = []
current: list[str] = []
i = 0
n = len(pattern)
while i < n:
ch = pattern[i]
if ch == "\\":
runs.append("".join(current))
current = []
i += 2
continue
if ch == "[":
runs.append("".join(current))
current = []
i += 1
while i < n and pattern[i] != "]":
i += 2 if pattern[i] == "\\" else 1
i += 1
continue
if ch in _REGEX_BREAKERS:
if ch in "*?{" and current:
current.pop()
runs.append("".join(current))
current = []
if ch == "{":
while i < n and pattern[i] != "}":
i += 1
i += 1
continue
current.append(ch)
i += 1
runs.append("".join(current))
best = max(runs, key=len, default="")
return best if len(best) >= _MIN_SEARCH_LITERAL else None
def search_query(pattern: str, fixed_string: bool) -> str | None:
"""Literal to push down to a code-search API for a grep/rg pattern.
Args:
pattern (str): the search pattern.
fixed_string (bool): True if -F is set.
Returns:
str | None: the pattern itself when it is literal, a required literal
extracted from a regex, or None when no literal can be searched.
"""
if classify_pattern(pattern, fixed_string) != PatternType.REGEX:
return pattern
return extract_required_literal(pattern)
def pattern_arg(texts: Sequence[str], flags: FlagView) -> str | None:
"""Resolve the pattern-list argument from -e values or the positional.
Args:
texts (Sequence[str]): positional TEXT operands.
flags (FlagView): typed view over raw flag kwargs.
Returns:
str | None: POSIX newline-joined pattern list (each -e value may
itself be a newline-separated list), or None when neither -e nor
a positional pattern was supplied.
"""
e_values = flags.list("e")
if e_values:
return "\n".join(e_values)
if texts:
return texts[0]
return None
async def resolve_pattern(
texts: Sequence[str],
flags: FlagView,
read_bytes: Callable[..., Awaitable[bytes]],
accessor: object,
index: IndexCacheStore | None,
usage: str,
) -> tuple[str, bool]:
"""Resolve the search pattern from -e/positional/-f flag arguments.
Args:
texts (Sequence[str]): positional TEXT operands.
flags (FlagView): typed view over raw flag kwargs.
read_bytes (Callable[..., Awaitable[bytes]]): whole-file reader used
for -f pattern files.
accessor (object): backend accessor for read_bytes.
index (IndexCacheStore | None): optional cache index.
usage (str): usage error message when no pattern was supplied.
Returns:
tuple[str, bool]: (newline-separated pattern list, never_match) where
never_match is True when -f supplied zero patterns (GNU: match
nothing; -F escaping must be skipped for the sentinel).
"""
pattern = pattern_arg(texts, flags)
pattern_file = flags.raw("f")
if isinstance(pattern_file, (PathSpec, list)):
files = (pattern_file
if isinstance(pattern_file, list) else [pattern_file])
for pf in files:
file_data = await call_read_bytes(read_bytes,
accessor,
pf,
index=index,
prefix=mount_prefix_of(
pf.virtual,
pf.resource_path))
pattern = merge_pattern_list(pattern, file_data)
if pattern is None:
return NEVER_MATCH, True
if pattern is None:
raise UsageError(usage)
return pattern, False
def merge_pattern_list(
pattern: str | None,
file_data: bytes | None,
) -> str | None:
"""Merge a pattern list with the content of a -f pattern file.
Args:
pattern (str | None): newline-separated pattern list from -e or the
positional argument, or None when only -f supplied patterns.
file_data (bytes | None): raw -f file content, or None without -f.
Returns:
str | None: merged newline-separated pattern list, or None when the
list is empty (GNU: zero patterns match nothing).
"""
parts: list[str] = [] if pattern is None else pattern.split("\n")
if file_data:
text = file_data.decode(errors="replace")
if text.endswith("\n"):
text = text[:-1]
parts.extend(text.split("\n"))
if not parts:
return None
return "\n".join(parts)
def build_pattern_str(
pattern: str,
fixed_string: bool = False,
whole_word: bool = False,
) -> str:
"""Build a regex source string from a POSIX pattern list.
Args:
pattern (str): newline-separated pattern list; a line matches when
any of the patterns matches.
fixed_string (bool): True if -F flag is set.
whole_word (bool): True if -w flag is set.
Returns:
str: regex source string.
"""
parts = pattern.split("\n")
if len(parts) == 1:
pat_str = re.escape(pattern) if fixed_string else pattern
if whole_word:
pat_str = r"\b" + pat_str + r"\b"
return pat_str
subs: list[str] = []
for part in parts:
sub = re.escape(part) if fixed_string else f"(?:{part})"
if whole_word:
sub = r"\b" + sub + r"\b"
subs.append(sub)
return "|".join(subs)
def compile_pattern(
pattern: str,
ignore_case: bool = False,
fixed_string: bool = False,
whole_word: bool = False,
) -> re.Pattern[str]:
flags = re.IGNORECASE if ignore_case else 0
return re.compile(build_pattern_str(pattern, fixed_string, whole_word),
flags)
def get_extension(path: str) -> str | None:
basename = path.rsplit("/", 1)[-1]
for ext in COMPOUND_EXTENSIONS:
if basename.endswith(ext):
return ext
dot = path.rfind(".")
if dot == -1 or "/" in path[dot:]:
return None
return path[dot:]
def grep_lines(
path: str,
data: list[str],
compiled: re.Pattern[str],
invert: bool,
line_numbers: bool,
count_only: bool,
files_only: bool,
only_matching: bool,
max_count: int | None,
) -> list[str]:
results: list[str] = []
count = 0
for i, line in enumerate(data, 1):
m = compiled.search(line)
matched = bool(m) != invert
if not matched:
continue
count += 1
if not count_only and not files_only:
if only_matching and m and not invert:
text = m.group(0)
else:
text = line
prefix = f"{i}:{text}" if line_numbers else text
results.append(prefix)
if max_count is not None and count >= max_count:
break
if count_only:
return [str(count)]
if files_only:
return [path] if count > 0 else []
return results
def grep_count_value(results: list[str]) -> int:
"""Return the numeric value from count-only grep results.
Args:
results (list[str]): `grep_lines(..., count_only=True)` output.
Returns:
int: The parsed match count, or zero when the result is empty.
"""
if not results:
return 0
return int(results[0])
def grep_count_has_matches(results: list[str]) -> bool:
"""Return whether count-only grep results contain any matches.
Args:
results (list[str]): `grep_lines(..., count_only=True)` output.
Returns:
bool: True when the parsed count is greater than zero.
"""
return grep_count_value(results) > 0
async def prefix_lines(source: AsyncIterator[bytes],
prefix: str) -> AsyncIterator[bytes]:
"""Prefix every line chunk with a filename label (grep -H).
Args:
source (AsyncIterator[bytes]): grep stream yielding one line per
chunk.
prefix (str): Label including the separator, e.g. ``file.txt:``.
"""
encoded = prefix.encode()
async for chunk in source:
yield encoded + chunk
async def nonzero_count_stream(
source: AsyncIterator[bytes]) -> AsyncIterator[bytes]:
"""Drop zero-count chunks for `rg -c` fallback streams.
Args:
source (AsyncIterator[bytes]): Count-only grep stream.
Yields:
bytes: Count chunks whose parsed value is greater than zero.
"""
async for chunk in source:
count = int(chunk.decode(errors="replace").strip() or "0")
if count > 0:
yield chunk
def count_records_have_matches(results: list[str]) -> bool:
"""Return whether any `path:count` record has a nonzero count.
Args:
results (list[str]): Count-only records in `path:count` form.
Returns:
bool: True when any parsed count is greater than zero.
"""
return any(int(r.rsplit(":", 1)[-1]) > 0 for r in results)
async def count_exit_stream(
source: AsyncIterator[bytes],
io: IOResult,
) -> AsyncIterator[bytes]:
"""Yield count-only grep output, setting exit 1 when all counts are zero.
GNU grep -c prints the count but still exits 1 when no lines were
selected, so emptiness-based exit detection cannot apply.
Args:
source (AsyncIterator[bytes]): Count-only grep stream.
io (IOResult): Result whose exit_code becomes 1 when nothing matched.
Yields:
bytes: The unchanged count chunks.
"""
any_match = False
async for chunk in source:
if int(chunk.decode(errors="replace").strip() or "0") > 0:
any_match = True
yield chunk
if not any_match:
io.exit_code = 1
async def grep_stream(
source: AsyncIterator[bytes],
pat: re.Pattern[str],
invert: bool = False,
line_numbers: bool = False,
only_matching: bool = False,
max_count: int | None = None,
count_only: bool = False,
after_context: int = 0,
before_context: int = 0,
) -> AsyncIterator[bytes]:
has_context = after_context > 0 or before_context > 0
if has_context and not count_only and not only_matching:
all_lines: list[str] = []
async for raw_line in AsyncLineIterator(source):
all_lines.append(raw_line.decode(errors="replace"))
for chunk in grep_context_lines(
all_lines,
pat,
invert,
line_numbers,
max_count,
after_context,
before_context,
):
yield chunk
return
match_count = 0
line_num = 0
async for raw_line in AsyncLineIterator(source):
line_num += 1
line = raw_line.decode(errors="replace")
hit = bool(pat.search(line))
if invert:
hit = not hit
if not hit:
continue
if only_matching and not invert:
for m in pat.finditer(line):
match_count += 1
if not count_only:
yield m.group().encode() + b"\n"
if max_count and match_count >= max_count:
if count_only:
yield str(match_count).encode() + b"\n"
return
else:
match_count += 1
if not count_only:
if line_numbers:
yield f"{line_num}:{line}\n".encode()
else:
yield raw_line + b"\n"
if max_count and match_count >= max_count:
if count_only:
yield str(match_count).encode() + b"\n"
return
if count_only:
yield str(match_count).encode() + b"\n"
async def grep_recursive(
readdir_fn: _AsyncReaddir,
stat_fn: _AsyncStat,
read_bytes_fn: _AsyncReadBytes,
path: str,
compiled: re.Pattern[str],
invert: bool,
line_numbers: bool,
count_only: bool,
files_only: bool,
only_matching: bool,
max_count: int | None,
warnings: list[str] | None = None,
read_stream_fn=None,
) -> list[str]:
results: list[str] = []
try:
entries = await readdir_fn(path)
except (FileNotFoundError, ValueError) as exc:
if warnings is not None:
warnings.append(f"grep: {path}: {exc}")
return results
for entry in entries:
try:
s = await stat_fn(entry)
except (FileNotFoundError, ValueError) as exc:
if warnings is not None:
warnings.append(f"grep: {entry}: {exc}")
continue
if s.type == FileType.DIRECTORY:
results.extend(await grep_recursive(
readdir_fn,
stat_fn,
read_bytes_fn,
entry,
compiled,
invert,
line_numbers,
count_only,
files_only,
only_matching,
max_count,
warnings,
read_stream_fn,
))
continue
if get_extension(entry) in BINARY_EXTENSIONS:
continue
if read_stream_fn is not None:
try:
source = read_stream_fn(entry)
file_results: list[str] = []
async for chunk in grep_stream(
source,
compiled,
invert=invert,
line_numbers=line_numbers,
only_matching=only_matching,
max_count=max_count,
count_only=count_only,
):
file_results.append(
chunk.decode(errors="replace").rstrip("\n"))
if count_only:
if file_results:
results.append(f"{entry}:{file_results[0]}")
elif files_only:
if file_results:
results.append(entry)
else:
results.extend(f"{entry}:{r}" for r in file_results)
except Exception as exc:
if warnings is not None:
warnings.append(f"grep: {entry}: {exc}")
continue
else:
try:
data = (await read_bytes_fn(entry)).decode(
errors="replace").splitlines()
file_results = grep_lines(
entry,
data,
compiled,
invert,
line_numbers,
count_only,
files_only,
only_matching,
max_count,
)
if count_only:
if file_results:
results.append(f"{entry}:{file_results[0]}")
elif files_only:
results.extend(file_results)
else:
results.extend(f"{entry}:{r}" for r in file_results)
except Exception as exc:
if warnings is not None:
warnings.append(f"grep: {entry}: {exc}")
continue
return results
async def grep_files_only(
readdir_fn: _AsyncReaddir,
stat_fn: _AsyncStat,
read_bytes_fn: _AsyncReadBytes,
path: str,
pattern: str,
recursive: bool,
ignore_case: bool,
invert: bool,
line_numbers: bool,
count_only: bool,
fixed_string: bool,
only_matching: bool,
max_count: int | None,
whole_word: bool,
warnings: list[str] | None,
read_stream_fn=None,
) -> list[str]:
compiled = compile_pattern(pattern, ignore_case, fixed_string, whole_word)
if recursive:
return await grep_recursive(
readdir_fn,
stat_fn,
read_bytes_fn,
path,
compiled,
invert,
line_numbers,
count_only,
True,
only_matching,
max_count,
warnings,
read_stream_fn,
)
try:
data = await read_bytes_fn(path)
text_lines = data.decode(errors="replace").splitlines()
count = 0
for _i, line in enumerate(text_lines, 1):
m = compiled.search(line)
matched = bool(m) != invert
if matched:
count += 1
if max_count is not None and count >= max_count:
break
if count_only:
return [str(count)]
return [path] if count > 0 else []
except (FileNotFoundError, ValueError, IsADirectoryError) as exc:
if warnings is not None:
warnings.append(f"grep: {path}: {exc}")
try:
s = await stat_fn(path)
if s.type == FileType.DIRECTORY:
return await grep_recursive(
readdir_fn,
stat_fn,
read_bytes_fn,
path,
compiled,
invert,
line_numbers,
count_only,
True,
only_matching,
max_count,
warnings,
read_stream_fn,
)
except (FileNotFoundError, ValueError) as exc:
if warnings is not None:
warnings.append(f"grep: {path}: {exc}")
try:
await readdir_fn(path)
return await grep_recursive(
readdir_fn,
stat_fn,
read_bytes_fn,
path,
compiled,
invert,
line_numbers,
count_only,
True,
only_matching,
max_count,
warnings,
read_stream_fn,
)
except (FileNotFoundError, ValueError) as exc2:
if warnings is not None:
warnings.append(f"grep: {path}: {exc2}")
return []