Skip to content

Commit 4e4439c

Browse files
author
patched.codes[bot]
committed
Patched patchwork/steps/ModifyCode/ModifyCode.py
1 parent da1370b commit 4e4439c

File tree

1 file changed

+102
-12
lines changed

1 file changed

+102
-12
lines changed

patchwork/steps/ModifyCode/ModifyCode.py

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,48 @@
11
from __future__ import annotations
22

33
from pathlib import Path
4+
from typing import Union, List
45

56
from patchwork.step import Step, StepStatus
67

78

8-
def save_file_contents(file_path, content):
9-
"""Utility function to save content to a file."""
10-
with open(file_path, "w") as file:
9+
def save_file_contents(file_path: str, content: Union[str, bytes]) -> None:
10+
"""Utility function to save content to a file in binary mode to preserve line endings.
11+
12+
Args:
13+
file_path: Path to the file to write
14+
content: Content to write, either as string or bytes. If string, it will be encoded as UTF-8."""
15+
with open(file_path, "wb") as file:
16+
# Convert string to bytes if needed
17+
if isinstance(content, str):
18+
content = content.encode('utf-8')
1119
file.write(content)
1220

1321

14-
def handle_indent(src: list[str], target: list[str], start: int, end: int) -> list[str]:
22+
def handle_indent(src: List[str], target: List[str], start: int, end: int) -> List[str]:
23+
"""Handles indentation of new code to match the original code's indentation level.
24+
25+
Args:
26+
src: Source lines from the original file
27+
target: New lines that need to be indented
28+
start: Start line number in the source file
29+
end: End line number in the source file
30+
31+
Returns:
32+
List of strings with proper indentation applied
33+
34+
Note:
35+
- If target is empty, returns it as is
36+
- If start equals end, uses start + 1 as end to ensure at least one line
37+
- Preserves existing indentation characters (spaces or tabs)
38+
"""
1539
if len(target) < 1:
1640
return target
1741

1842
if start == end:
1943
end = start + 1
2044

45+
# Find first non-empty line in source and target
2146
first_src_line = next((line for line in src[start:end] if line.strip() != ""), "")
2247
src_indent_count = len(first_src_line) - len(first_src_line.lstrip())
2348
first_target_line = next((line for line in target if line.strip() != ""), "")
@@ -26,36 +51,101 @@ def handle_indent(src: list[str], target: list[str], start: int, end: int) -> li
2651

2752
indent = ""
2853
if indent_diff > 0:
54+
# Use the same indentation character as the source (space or tab)
2955
indent_unit = first_src_line[0]
3056
indent = indent_unit * indent_diff
3157

3258
return [indent + line for line in target]
3359

3460

61+
def detect_line_ending(content: bytes) -> bytes:
62+
"""Detect the dominant line ending style in the given bytes content.
63+
64+
Args:
65+
content: File content in bytes to analyze
66+
67+
Returns:
68+
The detected line ending as bytes (b'\\r\\n', b'\\n', or b'\\r')
69+
70+
Note:
71+
- Counts occurrences of different line endings (CRLF, LF, CR)
72+
- Returns the most common line ending
73+
- Handles cases where \r\n is treated as one ending, not two
74+
- Defaults to \\n if no line endings are found"""
75+
crlf_count = content.count(b'\r\n')
76+
lf_count = content.count(b'\n') - crlf_count # Don't count \n that are part of \r\n
77+
cr_count = content.count(b'\r') - crlf_count # Don't count \r that are part of \r\n
78+
79+
if crlf_count > max(lf_count, cr_count):
80+
return b'\r\n'
81+
elif lf_count > cr_count:
82+
return b'\n'
83+
elif cr_count > 0:
84+
return b'\r'
85+
return b'\n' # Default to \n if no line endings found
86+
3587
def replace_code_in_file(
3688
file_path: str,
37-
start_line: int | None,
38-
end_line: int | None,
89+
start_line: Union[int, None],
90+
end_line: Union[int, None],
3991
new_code: str,
4092
) -> None:
93+
"""Replace specified lines in a file with new code while preserving line endings.
94+
95+
Args:
96+
file_path: Path to the file to modify
97+
start_line: Starting line number for replacement (0-based). If None, writes entire file
98+
end_line: Ending line number for replacement (0-based). If None, writes entire file
99+
new_code: New content to insert
100+
101+
Note:
102+
- Preserves the original file's line ending style (CRLF, LF, or CR)
103+
- Handles indentation to match the original code
104+
- Creates new file if it doesn't exist
105+
- Uses system default line ending for new files
106+
- Ensures all lines end with proper line ending
107+
- Preserves UTF-8 encoding
108+
"""
41109
path = Path(file_path)
110+
111+
# Convert new_code to use \n for initial splitting
42112
new_code_lines = new_code.splitlines(keepends=True)
43113
if len(new_code_lines) > 0 and not new_code_lines[-1].endswith("\n"):
44114
new_code_lines[-1] += "\n"
45115

46116
if path.exists() and start_line is not None and end_line is not None:
47117
"""Replaces specified lines in a file with new code."""
48-
text = path.read_text()
49-
118+
# Read file in binary mode to preserve original line endings
119+
with open(file_path, 'rb') as f:
120+
content = f.read()
121+
122+
# Detect original line ending
123+
line_ending = detect_line_ending(content)
124+
125+
# Decode content for line operations
126+
text = content.decode('utf-8')
50127
lines = text.splitlines(keepends=True)
51-
52-
# Insert the new code at the start line after converting it into a list of lines
128+
129+
# Handle indentation for new code lines
53130
lines[start_line:end_line] = handle_indent(lines, new_code_lines, start_line, end_line)
131+
132+
# Join all lines and encode ensuring all line endings match the original
133+
result = ''.join(lines)
134+
# Normalize to \n first
135+
result = result.replace('\r\n', '\n').replace('\r', '\n')
136+
# Then convert to detected line ending
137+
if line_ending == b'\r\n':
138+
result = result.replace('\n', '\r\n')
139+
elif line_ending == b'\r':
140+
result = result.replace('\n', '\r')
141+
142+
content = result.encode('utf-8')
54143
else:
55-
lines = new_code_lines
144+
# For new files, use system default line ending
145+
content = ''.join(new_code_lines).encode('utf-8')
56146

57147
# Save the modified contents back to the file
58-
save_file_contents(file_path, "".join(lines))
148+
save_file_contents(file_path, content)
59149

60150

61151
class ModifyCode(Step):

0 commit comments

Comments
 (0)