Skip to content

Commit a54d8e1

Browse files
committed
chore: Improve patch tool tolerance and reporting
Refactor `patch.tool.js` to be more robust against whitespace differences in patch markers and context lines. Also, enhance the reporting to detail how many segments were applied. Update version to 0.1.13. Added test files.
1 parent 0a1311a commit a54d8e1

5 files changed

Lines changed: 90 additions & 22 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tune-basic-toolset",
3-
"version": "0.1.12",
3+
"version": "0.1.13",
44
"description": "Basic toolset for tune",
55
"main": "src/index.js",
66
"files": [

src/patch.tool.js

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
const fs = require('fs').promises;
22

33
// Patch tool to apply custom diffs marked with <<<<<<< ORIGINAL and >>>>>>> UPDATED
4-
// Handles patches with context and applies only the segments between markers.
5-
4+
// More tolerant to whitespace differences on each line and reports per-block success.
65
module.exports = async function patch({ text, filename }, ctx) {
7-
// Regex to match each patch block
8-
// Be lenient about the number of conflict marker characters because some
9-
// environments may trim one or more > or < characters.
10-
const patchRegex = /<{6,}\s*ORIGINAL[^\n]*\n([\s\S]*?)=+\n([\s\S]*?)>{6,}\s*UPDATED[^\n]*(?:\n|$)/g;
11-
const patches = [];
12-
let match;
6+
if (!text || !filename) {
7+
return "No patch text or filename provided";
8+
}
139

14-
// Extract all old/new segments
15-
while ((match = patchRegex.exec(text)) !== null) {
16-
const oldPart = match[1].replace(/^\n+|\n+$/g, "");
17-
const newPart = match[2].replace(/^\n+|\n+$/g, "");
10+
// Match: <<<<<<< ORIGINAL ... ======= ... >>>>>>> UPDATED
11+
// Be tolerant to CRLF/LF and optional trailing text/spaces on the markers.
12+
const patchRegex = /<{6,}\s*ORIGINAL[^\n]*\r?\n([\s\S]*?)=+[^\n]*\r?\n([\s\S]*?)>{6,}\s*UPDATED[^\n]*(?:\r?\n|$)/g;
13+
14+
const patches = [];
15+
let m;
16+
while ((m = patchRegex.exec(text)) !== null) {
17+
const oldPart = String(m[1]).replace(/^\s*\r?\n+|\r?\n+\s*$/g, "");
18+
const newPart = String(m[2]).replace(/^\s*\r?\n+|\r?\n+\s*$/g, "");
1819
patches.push({ oldPart, newPart });
1920
}
2021

@@ -24,18 +25,41 @@ module.exports = async function patch({ text, filename }, ctx) {
2425

2526
let fileContent = await ctx.read(filename);
2627

27-
for (const { oldPart, newPart } of patches) {
28-
// Escape regex special chars in oldPart.
29-
// Do NOT relax all whitespace to \s+; that can swallow preceding newlines.
30-
// Only normalize line endings so CRLF in patches can match LF in files.
31-
let escaped = oldPart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28+
function buildPattern(oldStr) {
29+
// Escape special regex chars
30+
let escaped = oldStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31+
// Normalize line endings to \r?\n so CRLF/LF both match
3232
escaped = escaped.replace(/\r?\n/g, "\\r?\\n");
33-
const oldRegex = new RegExp(escaped, "g");
33+
// Tolerate indentation/space differences (spaces or tabs), zero-or-more
34+
// Keep newlines strict so structure must still match.
35+
escaped = escaped.replace(/[ \t]+/g, "[ \\t]*");
36+
return new RegExp(escaped, "g");
37+
}
38+
39+
const totalSegments = patches.length;
40+
let appliedSegments = 0;
41+
let totalReplacements = 0;
3442

35-
// Perform replacement using a function to avoid replacement string ambiguities
36-
fileContent = fileContent.replace(oldRegex, () => newPart);
43+
for (const { oldPart, newPart } of patches) {
44+
const re = buildPattern(oldPart);
45+
let matches = 0;
46+
fileContent = fileContent.replace(re, () => {
47+
matches += 1;
48+
return newPart;
49+
});
50+
if (matches > 0) {
51+
appliedSegments += 1;
52+
totalReplacements += matches;
53+
}
3754
}
3855

3956
await ctx.write(filename, fileContent);
40-
return "patched";
57+
58+
if (appliedSegments === 0) {
59+
return `no matches applied (0/${totalSegments})`;
60+
}
61+
if (appliedSegments < totalSegments) {
62+
return `patched partially (${appliedSegments}/${totalSegments}), replacements: ${totalReplacements}`;
63+
}
64+
return `patched (${appliedSegments}/${totalSegments}), replacements: ${totalReplacements}`;
4165
};

test/p14.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
a()
2+
b()
3+
-------------------------------
4+
<<<<<<< ORIGINAL
5+
a()
6+
b()
7+
=======
8+
A
9+
B
10+
>>>>>>> UPDATED
11+
-------------------------------
12+
A
13+
B

test/p15.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
foo(
2+
)
3+
-------------------------------
4+
<<<<<<< ORIGINAL
5+
foo(
6+
)
7+
=======
8+
X
9+
Y
10+
>>>>>>> UPDATED
11+
-------------------------------
12+
X
13+
Y

test/p16.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
aaa
2+
bbb
3+
ccc
4+
-------------------------------
5+
<<<<<<< ORIGINAL
6+
bbb
7+
=======
8+
B
9+
>>>>>>> UPDATED
10+
<<<<<<< ORIGINAL
11+
xxx
12+
=======
13+
X
14+
>>>>>>> UPDATED
15+
-------------------------------
16+
aaa
17+
B
18+
ccc

0 commit comments

Comments
 (0)