fix(tools): preserve each normalized replacement’s Unicode
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> (cherry picked from commit 8e18884e6b4638c8a91ba4fd678821e6812f6130)
This commit is contained in:
29
tests/tools/test_patch_unicode_replace_all.py
Normal file
29
tests/tools/test_patch_unicode_replace_all.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Replacing every normalized match preserves each region's untouched Unicode."""
|
||||
|
||||
import json
|
||||
|
||||
from tools import file_tools, terminal_tool
|
||||
from tools.registry import registry
|
||||
|
||||
|
||||
def test_replace_all_preserves_each_matches_unicode(tmp_path, monkeypatch):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
path = tmp_path / "notes.txt"
|
||||
original = "Price ‘old’—ready\nPrice 'old'—ready\n"
|
||||
path.write_text(original, encoding="utf-8")
|
||||
task = "unicode-replace-all"
|
||||
terminal_tool.register_task_env_overrides(task, {"env_type": "local", "cwd": str(tmp_path)})
|
||||
try:
|
||||
read = json.loads(registry.dispatch("read_file", {"path": str(path)}, task_id=task))
|
||||
assert "error" not in read
|
||||
result = json.loads(registry.dispatch("patch", {
|
||||
"mode": "replace", "path": str(path), "old_string": "Price 'old'--ready",
|
||||
"new_string": "Price 'new'--ready", "replace_all": True,
|
||||
}, task_id=task))
|
||||
assert "error" not in result, result
|
||||
assert path.read_text(encoding="utf-8") == original.replace("old", "new")
|
||||
finally:
|
||||
file_tools.clear_file_ops_cache(task)
|
||||
terminal_tool.clear_task_env_overrides(task)
|
||||
@@ -389,11 +389,10 @@ def fuzzy_find_and_replace(content: str, old_string: str, new_string: str,
|
||||
return content, 0, None, drift_err
|
||||
|
||||
effective_new = _maybe_unescape_new_string(new_string, content, matches)
|
||||
if strategy_name == "unicode_normalized":
|
||||
effective_new = _preserve_unicode_in_replacement(content, matches, old_string, effective_new)
|
||||
new_content = _apply_replacements(
|
||||
content, matches, effective_new,
|
||||
old_string=old_string if strategy_name != "exact" else None)
|
||||
old_string=old_string if strategy_name != "exact" else None,
|
||||
preserve_unicode=strategy_name == "unicode_normalized")
|
||||
_note_edit_match(strategy_name)
|
||||
return new_content, len(matches), strategy_name, None
|
||||
|
||||
@@ -575,14 +574,20 @@ def _preserve_unicode_in_replacement(content: str, matches: list[Span],
|
||||
|
||||
|
||||
def _apply_replacements(content: str, matches: list[Span],
|
||||
new_string: str, old_string: Optional[str] = None) -> str:
|
||||
new_string: str, old_string: Optional[str] = None,
|
||||
preserve_unicode: bool = False) -> str:
|
||||
"""Splice ``new_string`` over each span (end-to-start so offsets stay valid);
|
||||
``old_string`` non-None (non-exact match) re-indents it per region."""
|
||||
result = content
|
||||
for start, end in sorted(matches, key=lambda x: x[0], reverse=True):
|
||||
adjusted = new_string
|
||||
if old_string is not None:
|
||||
adjusted = _reindent_replacement(content[start:end], old_string, new_string)
|
||||
if preserve_unicode:
|
||||
# Each occurrence may use different typographic characters even
|
||||
# though all normalize to the same old_string.
|
||||
adjusted = _preserve_unicode_in_replacement(
|
||||
content, [(start, end)], old_string, adjusted)
|
||||
adjusted = _reindent_replacement(content[start:end], old_string, adjusted)
|
||||
result = result[:start] + adjusted + result[end:]
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user