refactor(src): split oversized modules
This commit is contained in:
@@ -3,7 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
import { applyUpdateHunk } from "./apply-patch-update.js";
|
||||
import { assertSandboxPath } from "./sandbox-paths.js";
|
||||
|
||||
const BEGIN_PATCH_MARKER = "*** Begin Patch";
|
||||
@@ -483,207 +483,3 @@ function parseUpdateFileChunk(
|
||||
|
||||
return { chunk, consumed: parsedLines + startIndex };
|
||||
}
|
||||
|
||||
async function applyUpdateHunk(
|
||||
filePath: string,
|
||||
chunks: UpdateFileChunk[],
|
||||
): Promise<string> {
|
||||
const originalContents = await fs.readFile(filePath, "utf8").catch((err) => {
|
||||
throw new Error(`Failed to read file to update ${filePath}: ${err}`);
|
||||
});
|
||||
|
||||
const originalLines = originalContents.split("\n");
|
||||
if (
|
||||
originalLines.length > 0 &&
|
||||
originalLines[originalLines.length - 1] === ""
|
||||
) {
|
||||
originalLines.pop();
|
||||
}
|
||||
|
||||
const replacements = computeReplacements(originalLines, filePath, chunks);
|
||||
let newLines = applyReplacements(originalLines, replacements);
|
||||
if (newLines.length === 0 || newLines[newLines.length - 1] !== "") {
|
||||
newLines = [...newLines, ""];
|
||||
}
|
||||
return newLines.join("\n");
|
||||
}
|
||||
|
||||
function computeReplacements(
|
||||
originalLines: string[],
|
||||
filePath: string,
|
||||
chunks: UpdateFileChunk[],
|
||||
): Array<[number, number, string[]]> {
|
||||
const replacements: Array<[number, number, string[]]> = [];
|
||||
let lineIndex = 0;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
if (chunk.changeContext) {
|
||||
const ctxIndex = seekSequence(
|
||||
originalLines,
|
||||
[chunk.changeContext],
|
||||
lineIndex,
|
||||
false,
|
||||
);
|
||||
if (ctxIndex === null) {
|
||||
throw new Error(
|
||||
`Failed to find context '${chunk.changeContext}' in ${filePath}`,
|
||||
);
|
||||
}
|
||||
lineIndex = ctxIndex + 1;
|
||||
}
|
||||
|
||||
if (chunk.oldLines.length === 0) {
|
||||
const insertionIndex =
|
||||
originalLines.length > 0 &&
|
||||
originalLines[originalLines.length - 1] === ""
|
||||
? originalLines.length - 1
|
||||
: originalLines.length;
|
||||
replacements.push([insertionIndex, 0, chunk.newLines]);
|
||||
continue;
|
||||
}
|
||||
|
||||
let pattern = chunk.oldLines;
|
||||
let newSlice = chunk.newLines;
|
||||
let found = seekSequence(
|
||||
originalLines,
|
||||
pattern,
|
||||
lineIndex,
|
||||
chunk.isEndOfFile,
|
||||
);
|
||||
|
||||
if (found === null && pattern[pattern.length - 1] === "") {
|
||||
pattern = pattern.slice(0, -1);
|
||||
if (newSlice.length > 0 && newSlice[newSlice.length - 1] === "") {
|
||||
newSlice = newSlice.slice(0, -1);
|
||||
}
|
||||
found = seekSequence(
|
||||
originalLines,
|
||||
pattern,
|
||||
lineIndex,
|
||||
chunk.isEndOfFile,
|
||||
);
|
||||
}
|
||||
|
||||
if (found === null) {
|
||||
throw new Error(
|
||||
`Failed to find expected lines in ${filePath}:\n${chunk.oldLines.join("\n")}`,
|
||||
);
|
||||
}
|
||||
|
||||
replacements.push([found, pattern.length, newSlice]);
|
||||
lineIndex = found + pattern.length;
|
||||
}
|
||||
|
||||
replacements.sort((a, b) => a[0] - b[0]);
|
||||
return replacements;
|
||||
}
|
||||
|
||||
function applyReplacements(
|
||||
lines: string[],
|
||||
replacements: Array<[number, number, string[]]>,
|
||||
): string[] {
|
||||
const result = [...lines];
|
||||
for (const [startIndex, oldLen, newLines] of [...replacements].reverse()) {
|
||||
for (let i = 0; i < oldLen; i += 1) {
|
||||
if (startIndex < result.length) {
|
||||
result.splice(startIndex, 1);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < newLines.length; i += 1) {
|
||||
result.splice(startIndex + i, 0, newLines[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function seekSequence(
|
||||
lines: string[],
|
||||
pattern: string[],
|
||||
start: number,
|
||||
eof: boolean,
|
||||
): number | null {
|
||||
if (pattern.length === 0) return start;
|
||||
if (pattern.length > lines.length) return null;
|
||||
|
||||
const maxStart = lines.length - pattern.length;
|
||||
const searchStart = eof && lines.length >= pattern.length ? maxStart : start;
|
||||
if (searchStart > maxStart) return null;
|
||||
|
||||
for (let i = searchStart; i <= maxStart; i += 1) {
|
||||
if (linesMatch(lines, pattern, i, (value) => value)) return i;
|
||||
}
|
||||
for (let i = searchStart; i <= maxStart; i += 1) {
|
||||
if (linesMatch(lines, pattern, i, (value) => value.trimEnd())) return i;
|
||||
}
|
||||
for (let i = searchStart; i <= maxStart; i += 1) {
|
||||
if (linesMatch(lines, pattern, i, (value) => value.trim())) return i;
|
||||
}
|
||||
for (let i = searchStart; i <= maxStart; i += 1) {
|
||||
if (
|
||||
linesMatch(lines, pattern, i, (value) =>
|
||||
normalizePunctuation(value.trim()),
|
||||
)
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function linesMatch(
|
||||
lines: string[],
|
||||
pattern: string[],
|
||||
start: number,
|
||||
normalize: (value: string) => string,
|
||||
): boolean {
|
||||
for (let idx = 0; idx < pattern.length; idx += 1) {
|
||||
if (normalize(lines[start + idx]) !== normalize(pattern[idx])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizePunctuation(value: string): string {
|
||||
return Array.from(value)
|
||||
.map((char) => {
|
||||
switch (char) {
|
||||
case "\u2010":
|
||||
case "\u2011":
|
||||
case "\u2012":
|
||||
case "\u2013":
|
||||
case "\u2014":
|
||||
case "\u2015":
|
||||
case "\u2212":
|
||||
return "-";
|
||||
case "\u2018":
|
||||
case "\u2019":
|
||||
case "\u201A":
|
||||
case "\u201B":
|
||||
return "'";
|
||||
case "\u201C":
|
||||
case "\u201D":
|
||||
case "\u201E":
|
||||
case "\u201F":
|
||||
return '"';
|
||||
case "\u00A0":
|
||||
case "\u2002":
|
||||
case "\u2003":
|
||||
case "\u2004":
|
||||
case "\u2005":
|
||||
case "\u2006":
|
||||
case "\u2007":
|
||||
case "\u2008":
|
||||
case "\u2009":
|
||||
case "\u200A":
|
||||
case "\u202F":
|
||||
case "\u205F":
|
||||
case "\u3000":
|
||||
return " ";
|
||||
default:
|
||||
return char;
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user