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

187 lines
6.8 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 shlex
from collections.abc import Callable
import tree_sitter
from mirage.shell.arith import evaluate_arith
from mirage.shell.call_stack import CallStack
from mirage.shell.errors import ArithError
from mirage.shell.types import NodeType as NT
from mirage.utils.path import expand_tilde
from mirage.workspace.expand.constants import ARITH_DELIMITERS, ARITH_OPERATORS
from mirage.workspace.expand.variable import _expand_braces, _lookup_var
from mirage.workspace.session import Session
from mirage.workspace.session.shell_dirs import home_dir
def _unescape_unquoted(text: str) -> str:
if "\\" not in text:
return text
try:
parts = shlex.split(text, posix=True)
except ValueError:
return text
return parts[0] if parts else text
async def expand_arith(
ts_node: tree_sitter.Node,
session: Session,
execute_fn: Callable,
call_stack: CallStack | None,
) -> str:
"""Reconstruct arithmetic expression text for the shared evaluator.
``$``-expansions substitute textually (bash performs expansions
before arithmetic evaluation), while bare variable names stay as
names so the evaluator can resolve and assign them
(``$(( y = 3 ))`` needs ``y``, not its value).
"""
parts = []
for child in ts_node.children:
if child.type in ARITH_DELIMITERS:
continue
if child.type in (NT.BINARY_EXPRESSION, NT.UNARY_EXPRESSION,
NT.PARENTHESIZED_EXPRESSION, NT.TERNARY_EXPRESSION,
NT.POSTFIX_EXPRESSION):
parts.append(await expand_arith(child, session, execute_fn,
call_stack))
elif child.type in ARITH_OPERATORS:
parts.append(child.text.decode())
elif child.type == NT.NUMBER:
parts.append(child.text.decode())
elif child.type in (NT.SIMPLE_EXPANSION, NT.EXPANSION,
NT.COMMAND_SUBSTITUTION):
parts.append(await expand_node(child, session, execute_fn,
call_stack))
elif child.type == NT.VARIABLE_NAME:
parts.append(child.text.decode())
else:
parts.append(await expand_node(child, session, execute_fn,
call_stack))
return " ".join(parts)
async def expand_node(
ts_node: tree_sitter.Node,
session: Session,
execute_fn: Callable,
call_stack: CallStack | None = None,
) -> str:
"""Expand a tree-sitter node to a string."""
ntype = ts_node.type
if ntype == NT.WORD:
word = _unescape_unquoted(ts_node.text.decode())
return expand_tilde(word, home_dir(session))
if ntype == NT.NUMBER:
return ts_node.text.decode()
if ntype == NT.COMMAND_NAME:
# The name is a word like any other: $CMD, "quoted", $(sub) all
# expand. A bare word has one named child (or none) and falls
# through to its own expansion rule.
for child in ts_node.named_children:
return await expand_node(child, session, execute_fn, call_stack)
return ts_node.text.decode()
if ntype == NT.SIMPLE_EXPANSION:
raw = ts_node.text.decode()
dollar = raw.rfind("$")
prefix = raw[:dollar]
var = raw[dollar + 1:]
return prefix + _lookup_var(var, session, call_stack)
if ntype == NT.EXPANSION:
return _expand_braces(ts_node, session.env,
getattr(session, "arrays", {}), call_stack)
if ntype == NT.COMMAND_SUBSTITUTION:
inner_cmds = [
c for c in ts_node.named_children
if c.type in (NT.COMMAND, NT.PIPELINE, NT.LIST,
NT.REDIRECTED_STATEMENT, NT.SUBSHELL)
]
if not inner_cmds:
return ""
inner = inner_cmds[0].text.decode()
io = await execute_fn(inner, session_id=session.session_id)
return (await io.stdout_str()).rstrip("\n")
if ntype == NT.ARITHMETIC_EXPANSION:
expr = await expand_arith(ts_node, session, execute_fn, call_stack)
try:
value, updates = evaluate_arith(expr, session.env)
except ArithError:
return ts_node.text.decode()
session.env.update(updates)
return str(value)
if ntype == NT.CONCATENATION:
parts = []
for child in ts_node.children:
parts.append(await expand_node(child, session, execute_fn,
call_stack))
return "".join(parts)
if ntype == NT.STRING:
parts = []
prev_end_row = None
for child in ts_node.children:
if child.type == NT.DQUOTE:
continue
if (prev_end_row is not None
and child.start_point[0] > prev_end_row):
parts.append("\n")
parts.append(await expand_node(child, session, execute_fn,
call_stack))
prev_end_row = child.end_point[0]
return "".join(parts)
if ntype == NT.STRING_CONTENT:
# Bash double-quote escapes: \$, \`, \", \\, \<newline>.
# Everything else preserves the backslash literally.
text = ts_node.text.decode()
text = text.replace("\\\\", "\x00")
text = text.replace('\\"', '"')
text = text.replace("\\$", "$")
text = text.replace("\\`", "`")
text = text.replace("\\\n", "")
text = text.replace("\x00", "\\")
return text
if ntype == NT.RAW_STRING:
raw = ts_node.text.decode()
return raw[1:-1]
if ntype == NT.VARIABLE_ASSIGNMENT:
raw = ts_node.text.decode()
if "=" in raw:
key, _, val_part = raw.partition("=")
val_nodes = [
c for c in ts_node.named_children if c.type != NT.VARIABLE_NAME
]
if val_nodes:
expanded = await expand_node(val_nodes[0], session, execute_fn,
call_stack)
return f"{key}={expanded}"
return f"{key}={val_part}"
return raw
return ts_node.text.decode()