main
py 66 lines 1.93 KB
Raw
1 from __future__ import annotations
2
3 from dataclasses import dataclass
4 from typing import Any, Literal
5
6
7 PatchMode = Literal["edits", "patch_text", "replace"]
8
9
10 @dataclass(frozen=True)
11 class PatchRequest:
12 mode: PatchMode
13 edits: Any = None
14 patch_text: str = ""
15 old_text: str = ""
16 new_text: str = ""
17
18
19 def parse_patch_request(
20 edits: Any,
21 patch_text: Any,
22 old_text: Any = None,
23 new_text: Any = None,
24 *,
25 both_error: str = "provide exactly one patch form: edits, patch_text, or old_text/new_text",
26 missing_error: str = "edits, patch_text, or old_text/new_text is required for patch",
27 ) -> tuple[PatchRequest | None, str]:
28 """Validate the mutually-exclusive patch request shape."""
29 has_edits = edits is not None
30 has_patch_text = patch_text is not None
31 has_replace = old_text is not None or new_text is not None
32 if sum([has_edits, has_patch_text, has_replace]) > 1:
33 return None, both_error
34
35 if has_replace:
36 old = str(old_text or "")
37 if not old:
38 return None, "old_text is required for exact replace"
39 return PatchRequest(
40 mode="replace",
41 old_text=old,
42 new_text=str(new_text or ""),
43 ), ""
44
45 if has_patch_text:
46 text = str(patch_text)
47 if not text.strip():
48 return None, "patch_text must not be empty"
49 return PatchRequest(mode="patch_text", patch_text=text), ""
50
51 if not edits:
52 return None, missing_error
53
54 return PatchRequest(mode="edits", edits=edits), ""
55
56
57 def exact_replace_to_patch_text(path: str, old_text: str, new_text: str) -> str:
58 """Represent one exact text replacement as a context patch."""
59 lines = [
60 "*** Begin Patch",
61 f"*** Update File: {path}",
62 ]
63 lines.extend(f"-{line}" for line in old_text.split("\n"))
64 lines.extend(f"+{line}" for line in new_text.split("\n"))
65 lines.append("*** End Patch")
66 return "\n".join(lines)