修复:输出前缀绑定替换保留原引号,避免 Python -c 字符串字面量变裸标识符(zh01 NameError)
This commit is contained in:
@@ -1536,6 +1536,20 @@ def _shell_word_pattern(literal: str) -> re.Pattern[str]:
|
||||
)
|
||||
|
||||
|
||||
def _shell_word_replacement(replacement: str) -> Callable[[re.Match[str]], str]:
|
||||
"""替换函数:保留原文中字面量自带的引号。
|
||||
|
||||
``shlex.quote`` 在 shell 上下文是正确的,但 ``python -c "...'a.fa'..."`` 里的
|
||||
字符串字面量替换后必须保留引号,否则会变成裸标识符导致 NameError。
|
||||
"""
|
||||
|
||||
def _replace(match: re.Match[str]) -> str:
|
||||
quote = match.group("quote") or ""
|
||||
return f"{quote}{replacement}{quote}"
|
||||
|
||||
return _replace
|
||||
|
||||
|
||||
def _script_positional_args(contract: dict[str, Any], script: str) -> list[str]:
|
||||
try:
|
||||
tokens = shlex.split(str(contract.get("original_command") or ""))
|
||||
@@ -1710,7 +1724,7 @@ def _render_script_updates(
|
||||
"PATCH_TARGET_INVALID", f"invalid match count for {literal}"
|
||||
)
|
||||
updated, count = _shell_word_pattern(literal).subn(
|
||||
shlex.quote(replacement), text
|
||||
_shell_word_replacement(replacement), text
|
||||
)
|
||||
if count < expected:
|
||||
raise RuntimeBindingError(
|
||||
|
||||
@@ -1528,3 +1528,31 @@ def test_replay_isolates_symlink_input_before_source_overwrite(
|
||||
}
|
||||
]
|
||||
assert result["execution_work_tree"]["Ft.input.txt"]["type"] == "regular"
|
||||
|
||||
|
||||
def test_shell_word_replacement_preserves_python_string_literal_quotes() -> None:
|
||||
"""zh01 回归:python -c 里带引号的输出路径被前缀替换后必须保留引号。
|
||||
|
||||
替换前:SeqIO.write(...,'combined.pep.fasta','fasta')
|
||||
若用 shlex.quote 替换会变成 SeqIO.write(...,output.fasta,'fasta')
|
||||
→ Python 报 NameError: name 'output' is not defined。
|
||||
"""
|
||||
pattern = runtime_module._shell_word_pattern("combined.pep.fasta")
|
||||
replace = runtime_module._shell_word_replacement("output.fasta")
|
||||
source = (
|
||||
"python -c \"from Bio import SeqIO; "
|
||||
"SeqIO.write(list(SeqIO.parse('Ft.SPL.pep.fasta','fasta')),"
|
||||
"'combined.pep.fasta','fasta')\" && "
|
||||
"muscle -in combined.pep.fasta -out combined.pep.mfa"
|
||||
)
|
||||
updated, count = pattern.subn(replace, source)
|
||||
assert count == 2
|
||||
assert "'output.fasta'" in updated
|
||||
assert "'combined.pep.fasta'" not in updated
|
||||
# 无引号的 shell 单词路径不受影响(不引入引号)
|
||||
shell_text = "muscle -in combined.pep.mfa -out combined.pep.mfa2"
|
||||
updated_shell, shell_count = runtime_module._shell_word_pattern(
|
||||
"combined.pep.mfa"
|
||||
).subn(runtime_module._shell_word_replacement("output.mfa"), shell_text)
|
||||
assert shell_count == 1
|
||||
assert "-in output.mfa" in updated_shell
|
||||
|
||||
Reference in New Issue
Block a user