9201ef759e
CI / lint (push) Waiting to run
CI / mypy (push) Waiting to run
CI / docs (push) Waiting to run
CI / test on 3.10 (standard) (push) Waiting to run
CI / test on 3.11 (standard) (push) Waiting to run
CI / test on 3.12 (standard) (push) Waiting to run
CI / test on 3.10 (all-extras) (push) Waiting to run
CI / test on 3.11 (all-extras) (push) Waiting to run
CI / test on 3.12 (all-extras) (push) Waiting to run
CI / test on 3.13 (all-extras) (push) Waiting to run
CI / test on 3.14 (pydantic-evals) (push) Waiting to run
Harness Compat / harness compat (push) Waiting to run
CI / test on 3.13 (standard) (push) Waiting to run
CI / test on 3.14 (standard) (push) Waiting to run
CI / test on 3.14 (all-extras) (push) Waiting to run
CI / test on 3.10 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.11 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.12 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.13 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.14 (pydantic-ai-slim) (push) Waiting to run
CI / test on 3.10 (pydantic-evals) (push) Waiting to run
CI / test on 3.11 (pydantic-evals) (push) Waiting to run
CI / test on 3.12 (pydantic-evals) (push) Waiting to run
CI / test on 3.13 (pydantic-evals) (push) Waiting to run
CI / test on 3.10 (lowest-versions) (push) Waiting to run
CI / test on 3.11 (lowest-versions) (push) Waiting to run
CI / test on 3.12 (lowest-versions) (push) Waiting to run
CI / test on 3.13 (lowest-versions) (push) Waiting to run
CI / test on 3.14 (lowest-versions) (push) Waiting to run
CI / test examples on 3.11 (push) Waiting to run
CI / test examples on 3.12 (push) Waiting to run
CI / test examples on 3.13 (push) Waiting to run
CI / test examples on 3.14 (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / check (push) Blocked by required conditions
CI / deploy-docs (push) Blocked by required conditions
CI / deploy-docs-preview (push) Blocked by required conditions
CI / build release artifacts (push) Blocked by required conditions
CI / publish to PyPI (push) Blocked by required conditions
CI / Send tweet (push) Blocked by required conditions
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
"""Claude's `MultiEdit` tool — apply multiple string replacements to one file atomically."""
|
|
|
|
# pydantic requires typing_extensions.TypedDict (not typing.TypedDict) for
|
|
# schema generation on Python < 3.12; typing_extensions ships with pydantic.
|
|
from typing_extensions import NotRequired, TypedDict
|
|
|
|
from .shared import attach_context, resolve
|
|
|
|
|
|
class EditOp(TypedDict):
|
|
"""One replacement for `MultiEdit` (Claude's edit schema)."""
|
|
|
|
old_string: str
|
|
new_string: str
|
|
replace_all: NotRequired[bool]
|
|
|
|
|
|
def multi_edit(file_path: str, edits: list[EditOp]) -> str:
|
|
"""Apply a sequence of string replacements to one workspace file atomically.
|
|
|
|
Each edit replaces the first occurrence of `old_string` (or every
|
|
occurrence when `replace_all`). If any `old_string` is missing, nothing is
|
|
written — the file is left untouched.
|
|
"""
|
|
try:
|
|
p = resolve(file_path)
|
|
text = original = p.read_text(encoding='utf-8')
|
|
except OSError as exc:
|
|
return f'error: {exc}'
|
|
for i, e in enumerate(edits):
|
|
old = e.get('old_string', '')
|
|
if not old or old not in text:
|
|
return f'error: edit #{i + 1} `old_string` not found (no changes written)'
|
|
text = text.replace(old, e.get('new_string', ''), -1 if e.get('replace_all') else 1)
|
|
if text == original:
|
|
return 'no changes'
|
|
try:
|
|
p.write_text(text, encoding='utf-8')
|
|
except OSError as exc:
|
|
return f'error: {exc}'
|
|
return attach_context(file_path) + f'applied {len(edits)} edit(s) to {p}'
|