@plurnk/plurnk-mimetypes-text-diff 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PossumTech Laboratories, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @plurnk/plurnk-mimetypes-text-diff
2
+
3
+ `text/x-diff` mimetype handler for the [plurnk](https://github.com/plurnk) ecosystem. Parses unified, plain (`diff -u`), and combined/merge (`diff --cc`) diffs with a hand-rolled line-budget scanner — **no parser dependency**.
4
+
5
+ ## install
6
+
7
+ ```
8
+ npm i @plurnk/plurnk-mimetypes-text-diff
9
+ ```
10
+
11
+ ## what it does
12
+
13
+ - **`extractRaw(content)`** emits one `module` symbol per file section and one `field` symbol per hunk (see the symbol model below).
14
+ - **`deepJson(content)`** emits the structural tree of target-file ranges and per-file stats — the jsonpath query target. The framework projects it to deep-xml automatically, so xpath works too.
15
+
16
+ Both channels are backed by a single `scanDiff(text)` pass (exported for re-use). `validate` is the framework default (a diff has no fail-hard syntax check). `content` (HTML-only) and `references` are not implemented.
17
+
18
+ ## why hand-rolled (Tier 4)
19
+
20
+ A unified diff is rigidly line-oriented, but the one fact that disambiguates it is **not context-free**. The hunk header `@@ -a,b +c,d @@` declares a **line budget**: `b` old slots + `d` new slots. Inside an open hunk, while the budget is unspent, every line's leading `+`/`-`/space columns are *authoritative content* — regardless of what text follows the prefix.
21
+
22
+ This budget is exactly what `tree-sitter-diff` cannot track, and it fails on two real inputs:
23
+
24
+ 1. **The `--` trap.** A hunk that *deletes* a line whose content starts with `---` (e.g. removing `--- old config`) is indistinguishable from a new file header to a context-free grammar — the grammar has no way to know it's still inside the prior hunk's budget. tree-sitter-diff mis-classifies it as the start of a new file section. The line budget resolves it: the line is consumed as a deletion because the open hunk still owes content slots. This is the test that justifies the hand-roll (`src/TextDiff.test.ts`, "the `--`-content-deletion trap").
25
+ 2. **Combined/merge diffs ERROR.** `diff --cc` / `diff --combined` use an `@@@` fence with N parent ranges and multi-column `+`/`-` prefixes; conflict-marker lines (`<<<<<<<`, `=======`, `>>>>>>>`) appear as additions. tree-sitter-diff produces an ERROR node on these outright. The scanner handles them by summing every range's count into one budget and reading `fenceWidth` prefix columns per line.
26
+
27
+ A ~53-LOC prototype of this scanner matched `git diff --shortstat` **byte-exact** (files, insertions, deletions) on a 10,894-line real diff. The production scanner here is verified against the same kind of real `git diff` output.
28
+
29
+ The scanner also handles: `diff --git a/X b/Y` sections **and** bare `---`/`+++` pairs; `a/ b/ c/ i/ w/` prefix stripping; `/dev/null`; timestamp tails on `---`/`+++` lines; renames (`rename from`/`rename to` → one `old → new` section); mode-change-only and binary sections (paths recovered from the `diff --git` line); per-file add/del counts; truncation (budget never satisfied → close cleanly); interleaved garbage (no header match → skip; mid-hunk garbage → close hunk, rescan); and `` (counts no budget).
30
+
31
+ ## symbol model
32
+
33
+ | Diff element | `MimeSymbol` kind | name |
34
+ |---|---|---|
35
+ | File section | `module` | new path (a/b-stripped); old path for pure deletions; `old → new` for renames |
36
+ | Hunk | `field` | git's section-heading text when present (`function greet(name) {` — more useful to a model than coordinates, and name-joinable to the source entry), else `@@ +c,d` (the new-file coordinates the model navigates to) |
37
+
38
+ A hunk is a `field` (not a `function`) because hunks have no params and aren't callable — `field` renders as a member of the file module (the hunk's `container` is the file's name). `line`/`endLine` span the section / hunk **within the diff document** (where the reader navigates), not the target file.
39
+
40
+ ## deepJson shape
41
+
42
+ The deep channel carries the **target-file** ranges and per-file stats — jsonpath-queryable, each object carrying a `type` so the framework's `projectJsonToXml` names elements cleanly:
43
+
44
+ ```ts
45
+ {
46
+ type: "diff",
47
+ files: [{
48
+ type: "file",
49
+ oldPath, newPath, line, endLine, additions, deletions, binary,
50
+ hunks: [{
51
+ type: "hunk",
52
+ oldStart, oldCount, newStart, newCount, heading /* string | null */,
53
+ line, endLine,
54
+ }],
55
+ }],
56
+ }
57
+ ```
58
+
59
+ `extent` is `totalLines` (the framework default — correct for a line-oriented format).
60
+
61
+ ## references
62
+
63
+ Refs-free for v1. References are a code-graph concept (`call`, `import`, `inherit`, …) that a diff doesn't natively express. A future **file-path use-edge** is plausible — a diff's touched paths are in-corpus entry paths, so the diff could link to the entries it patches — but that edge is not built here (precision over speculation).
64
+
65
+ ## detection
66
+
67
+ Resolves on the `.diff` and `.patch` extensions. Extensionless git output (e.g. piped `git diff`) is not detected by extension and falls to the consumer's `defaultMimetype` (plurnk-service); magic-byte / content sniffing is the framework's §8 future lane, not this handler's job.
68
+
69
+ ## license
70
+
71
+ MIT.
@@ -0,0 +1,31 @@
1
+ import { BaseHandler } from "@plurnk/plurnk-mimetypes";
2
+ import type { HandlerContent, MimeSymbol } from "@plurnk/plurnk-mimetypes";
3
+ export default class TextDiff extends BaseHandler {
4
+ extractRaw(content: HandlerContent): MimeSymbol[];
5
+ deepJson(content: HandlerContent): unknown;
6
+ }
7
+ export interface DiffHunk {
8
+ oldStart: number;
9
+ oldCount: number;
10
+ newStart: number;
11
+ newCount: number;
12
+ heading: string | null;
13
+ line: number;
14
+ endLine: number;
15
+ }
16
+ export interface DiffFile {
17
+ oldPath: string | null;
18
+ newPath: string | null;
19
+ rename: boolean;
20
+ binary: boolean;
21
+ additions: number;
22
+ deletions: number;
23
+ hunks: DiffHunk[];
24
+ line: number;
25
+ endLine: number;
26
+ }
27
+ export interface DiffScan {
28
+ files: DiffFile[];
29
+ }
30
+ export declare function scanDiff(text: string): DiffScan;
31
+ //# sourceMappingURL=TextDiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextDiff.d.ts","sourceRoot":"","sources":["../src/TextDiff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAgC3E,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,WAAW;IACpC,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,EAAE;IAwBjD,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO;CA0BtD;AAgBD,MAAM,WAAW,QAAQ;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACrB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACrB;AAsED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CA0I/C"}
@@ -0,0 +1,389 @@
1
+ import { BaseHandler } from "@plurnk/plurnk-mimetypes";
2
+ // text/x-diff handler (Tier 4 hand-roll, no parser dependency).
3
+ //
4
+ // Why hand-rolled — tree-sitter-diff is empirically disqualified. A unified
5
+ // diff is rigidly line-oriented, and the one fact that disambiguates it is
6
+ // NON-context-free: the hunk header `@@ -a,b +c,d @@` declares a LINE BUDGET
7
+ // (b old slots + d new slots). Only by tracking remaining budget can a `-`
8
+ // line whose content begins with `--` (e.g. deleting `--- old config`) be
9
+ // known to be a deletion rather than the start of a new file header. A
10
+ // context-free grammar can't count that budget, so tree-sitter-diff
11
+ // mis-classifies; and on combined/merge diffs (`diff --cc`, `@@@`) it ERRORs
12
+ // outright. A focused line scanner has neither problem. (plurnk-mimetypes#0
13
+ // probe, 2026-06: a ~53-LOC prototype matched `git diff --shortstat`
14
+ // byte-exact on a 10,894-line real diff.)
15
+ //
16
+ // Symbol model: each file section → `module` (name = new path, or `old → new`
17
+ // for renames, or old path for pure deletions). Each hunk → `field` (a member
18
+ // of the file; hunks aren't callable), named by git's section-heading text
19
+ // when present (`function greet(name) {` — name-joinable to source entries),
20
+ // else by its new-file coordinates `@@ +c,d`. line/endLine span the section /
21
+ // hunk WITHIN THE DIFF DOCUMENT (not the target file).
22
+ //
23
+ // deepJson carries the target-file ranges + per-file stats for jsonpath: a
24
+ // `{ type:"diff", files:[{ type:"file", ..., hunks:[{ type:"hunk", ... }] }] }`
25
+ // tree that projects cleanly to deep-xml (each object's `type` names the
26
+ // element; oldStart/newStart/counts/heading become queryable child elements).
27
+ //
28
+ // References are deferred (refs-free for v1). A future file-path use-edge is
29
+ // plausible — entry paths are in-corpus, so a diff's touched paths could link
30
+ // to the entries they patch — but it is NOT built here (precision over
31
+ // speculation).
32
+ export default class TextDiff extends BaseHandler {
33
+ extractRaw(content) {
34
+ const { files } = scanDiff(toText(content));
35
+ const out = [];
36
+ for (const f of files) {
37
+ const name = sectionName(f);
38
+ out.push({
39
+ name,
40
+ kind: "module",
41
+ line: f.line,
42
+ endLine: f.endLine,
43
+ });
44
+ for (const h of f.hunks) {
45
+ out.push({
46
+ name: h.heading ?? `@@ +${h.newStart},${h.newCount}`,
47
+ kind: "field",
48
+ line: h.line,
49
+ endLine: h.endLine,
50
+ container: name,
51
+ });
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ deepJson(content) {
57
+ const { files } = scanDiff(toText(content));
58
+ return {
59
+ type: "diff",
60
+ files: files.map((f) => ({
61
+ type: "file",
62
+ oldPath: f.oldPath,
63
+ newPath: f.newPath,
64
+ line: f.line,
65
+ endLine: f.endLine,
66
+ additions: f.additions,
67
+ deletions: f.deletions,
68
+ binary: f.binary,
69
+ hunks: f.hunks.map((h) => ({
70
+ type: "hunk",
71
+ oldStart: h.oldStart,
72
+ oldCount: h.oldCount,
73
+ newStart: h.newStart,
74
+ newCount: h.newCount,
75
+ heading: h.heading,
76
+ line: h.line,
77
+ endLine: h.endLine,
78
+ })),
79
+ })),
80
+ };
81
+ }
82
+ }
83
+ function toText(content) {
84
+ return typeof content === "string"
85
+ ? content
86
+ : new TextDecoder("utf-8").decode(content);
87
+ }
88
+ // File-section symbol name: `old → new` for renames, old path for pure
89
+ // deletions (new is /dev/null), else the new path.
90
+ function sectionName(f) {
91
+ if (f.rename && f.oldPath && f.newPath)
92
+ return `${f.oldPath} → ${f.newPath}`;
93
+ if (f.newPath === null && f.oldPath !== null)
94
+ return f.oldPath;
95
+ return f.newPath ?? f.oldPath ?? "";
96
+ }
97
+ // Strip a/ b/ c/ i/ w/ prefix from a diff path token; map /dev/null to null;
98
+ // drop the timestamp tail git/diff append to ---/+++ lines (tab- or
99
+ // space-separated). Quoted paths (git quotes paths with special chars) are
100
+ // unquoted.
101
+ function cleanPath(raw) {
102
+ let p = raw;
103
+ // Strip a trailing timestamp/metadata after a tab (the canonical
104
+ // separator) — `--- a/file.c\t2026-01-01 12:00:00.000 +0000`.
105
+ const tab = p.indexOf("\t");
106
+ if (tab >= 0)
107
+ p = p.slice(0, tab);
108
+ p = p.trim();
109
+ if (p.startsWith('"') && p.endsWith('"') && p.length >= 2)
110
+ p = p.slice(1, -1);
111
+ if (p === "/dev/null")
112
+ return null;
113
+ const m = /^[abciw]\/(.*)$/.exec(p);
114
+ if (m)
115
+ return m[1];
116
+ return p;
117
+ }
118
+ // Parse a unified hunk header `@@ -a,b +c,d @@ heading` or a combined header
119
+ // `@@@ -a,b -e,f +c,d @@@ heading` (N parents → N old ranges, one new range).
120
+ // Returns null on no match. The line budget is the SUM of every range's count
121
+ // (each old range and the new range consumes one slot per content line in the
122
+ // hunk body).
123
+ function parseHunkHeader(line) {
124
+ const m = /^(@{2,})\s+(.*?)\s+\1(.*)$/.exec(line);
125
+ if (!m)
126
+ return null;
127
+ const fence = m[1];
128
+ const ranges = m[2].trim().split(/\s+/);
129
+ const heading = m[3].length > 0 ? m[3].replace(/^\s+/, "") : null;
130
+ const fenceWidth = fence.length - 1; // N parents for combined; 1 for plain
131
+ let firstOld = null;
132
+ let last = null;
133
+ let budget = 0;
134
+ for (const r of ranges) {
135
+ const rm = /^[-+](\d+)(?:,(\d+))?$/.exec(r);
136
+ if (!rm)
137
+ return null;
138
+ const start = Number(rm[1]);
139
+ const count = rm[2] === undefined ? 1 : Number(rm[2]);
140
+ budget += count;
141
+ if (r.startsWith("-") && firstOld === null)
142
+ firstOld = { start, count };
143
+ last = { start, count };
144
+ }
145
+ if (firstOld === null || last === null)
146
+ return null;
147
+ return {
148
+ oldStart: firstOld.start,
149
+ oldCount: firstOld.count,
150
+ newStart: last.start,
151
+ newCount: last.count,
152
+ budget,
153
+ heading,
154
+ fenceWidth,
155
+ };
156
+ }
157
+ // Single line-oriented pass. The one piece of state is the hunk line budget
158
+ // (remaining old+new slots). While a hunk is open with budget remaining, lines
159
+ // whose leading `fenceWidth` columns are `+`/`-`/space are authoritative hunk
160
+ // content — consumed verbatim. That authority is what resolves the
161
+ // `--`-content ambiguity a grammar can't.
162
+ export function scanDiff(text) {
163
+ const lines = text.split("\n");
164
+ // A trailing newline yields a final empty element; drop it so it doesn't
165
+ // inflate spans (it is the line terminator, not a line).
166
+ if (lines.length > 0 && lines[lines.length - 1] === "")
167
+ lines.pop();
168
+ const files = [];
169
+ let file = null;
170
+ let hunk = null;
171
+ let budget = 0;
172
+ let fenceWidth = 1;
173
+ let pendingOld = null; // a `---` seen, awaiting its `+++`
174
+ const closeHunk = (endLine) => {
175
+ if (hunk) {
176
+ hunk.endLine = endLine;
177
+ hunk = null;
178
+ }
179
+ budget = 0;
180
+ };
181
+ const closeFile = (endLine) => {
182
+ closeHunk(endLine);
183
+ if (file) {
184
+ file.endLine = endLine;
185
+ file = null;
186
+ }
187
+ pendingOld = null;
188
+ };
189
+ const openFile = (lineNo) => {
190
+ closeFile(lineNo - 1);
191
+ const f = {
192
+ oldPath: null, newPath: null, rename: false, binary: false,
193
+ additions: 0, deletions: 0, hunks: [], line: lineNo, endLine: lineNo,
194
+ };
195
+ files.push(f);
196
+ return f;
197
+ };
198
+ for (let i = 0; i < lines.length; i += 1) {
199
+ const line = lines[i];
200
+ const lineNo = i + 1;
201
+ // Inside an open hunk with budget remaining, prefix columns are
202
+ // authoritative. This MUST be tested before any header match so a
203
+ // deletion of `--- old config` is consumed as content, not read as a
204
+ // file header (the trap that justifies the hand-roll).
205
+ if (hunk && budget > 0) {
206
+ const prefix = line.slice(0, fenceWidth);
207
+ // `` is a non-content annotation; it
208
+ // belongs to the preceding +/- line and consumes no budget.
209
+ if (line.startsWith("\\"))
210
+ continue;
211
+ if (isHunkContent(prefix, fenceWidth)) {
212
+ budget -= countSlots(prefix, fenceWidth);
213
+ if (file) {
214
+ if (prefix.includes("+"))
215
+ file.additions += 1;
216
+ if (prefix.includes("-"))
217
+ file.deletions += 1;
218
+ }
219
+ file.endLine = lineNo;
220
+ hunk.endLine = lineNo;
221
+ if (budget <= 0)
222
+ closeHunk(lineNo);
223
+ continue;
224
+ }
225
+ // Mid-hunk garbage (no valid prefix): close the hunk and re-scan
226
+ // this line as a potential header below.
227
+ closeHunk(lineNo - 1);
228
+ }
229
+ // `diff --git a/X b/Y` (or `diff --cc`/`--combined`) opens a section
230
+ // and recovers both paths (survives binary / mode-only sections that
231
+ // carry no ---/+++ pair).
232
+ const git = /^diff --(?:git|cc|combined)\s+(.*)$/.exec(line);
233
+ if (git) {
234
+ const f = openFile(lineNo);
235
+ file = f;
236
+ const paths = parseGitPaths(git[1]);
237
+ if (paths) {
238
+ f.oldPath = paths.old;
239
+ f.newPath = paths.new;
240
+ }
241
+ continue;
242
+ }
243
+ // Extended headers within a section.
244
+ if (file && !hunk) {
245
+ const ren = /^rename (from|to) (.+)$/.exec(line);
246
+ if (ren) {
247
+ file.rename = true;
248
+ if (ren[1] === "from")
249
+ file.oldPath = cleanPath(ren[2]);
250
+ else
251
+ file.newPath = cleanPath(ren[2]);
252
+ file.endLine = lineNo;
253
+ continue;
254
+ }
255
+ if (/^Binary files /.test(line) || /^GIT binary patch/.test(line)) {
256
+ file.binary = true;
257
+ file.endLine = lineNo;
258
+ continue;
259
+ }
260
+ }
261
+ // `--- old` line. Inside a git section it refines the old path; with no
262
+ // open section (bare `diff -u` output) it OPENS one.
263
+ const minus = /^--- (.*)$/.exec(line);
264
+ if (minus) {
265
+ if (!file) {
266
+ file = openFile(lineNo);
267
+ }
268
+ file.oldPath = cleanPath(minus[1]);
269
+ file.endLine = lineNo;
270
+ pendingOld = minus[1];
271
+ continue;
272
+ }
273
+ // `+++ new` line completes the ---/+++ pair.
274
+ const plus = /^\+\+\+ (.*)$/.exec(line);
275
+ if (plus && pendingOld !== null) {
276
+ file.newPath = cleanPath(plus[1]);
277
+ file.endLine = lineNo;
278
+ pendingOld = null;
279
+ continue;
280
+ }
281
+ // Hunk header. Opens a hunk and seeds the line budget.
282
+ const hh = parseHunkHeader(line);
283
+ if (hh && file) {
284
+ closeHunk(lineNo - 1);
285
+ const h = {
286
+ oldStart: hh.oldStart, oldCount: hh.oldCount,
287
+ newStart: hh.newStart, newCount: hh.newCount,
288
+ heading: hh.heading, line: lineNo, endLine: lineNo,
289
+ };
290
+ file.hunks.push(h);
291
+ hunk = h;
292
+ budget = hh.budget;
293
+ fenceWidth = hh.fenceWidth;
294
+ file.endLine = lineNo;
295
+ continue;
296
+ }
297
+ // Other git extended headers (index, mode, similarity, new/deleted
298
+ // file mode, etc.) extend the section span without their own meaning.
299
+ if (file && !hunk && isExtendedHeader(line)) {
300
+ file.endLine = lineNo;
301
+ continue;
302
+ }
303
+ // Interleaved garbage with no open section: skip.
304
+ }
305
+ closeFile(lines.length);
306
+ return { files };
307
+ }
308
+ // A combined diff's prefix is `fenceWidth` columns, each `+`/`-`/space. A
309
+ // plain diff is one column. An all-space prefix is a context line; any `+`/`-`
310
+ // makes it an add/del column.
311
+ function isHunkContent(prefix, fenceWidth) {
312
+ if (prefix.length < fenceWidth)
313
+ return false;
314
+ for (let i = 0; i < fenceWidth; i += 1) {
315
+ const c = prefix[i];
316
+ if (c !== "+" && c !== "-" && c !== " ")
317
+ return false;
318
+ }
319
+ return true;
320
+ }
321
+ // Budget accounting: a context line (all spaces) consumes one old + one new
322
+ // slot per column-pair, but for budget purposes we count it as touching every
323
+ // range, i.e. fenceWidth + 1 slots (each parent's old range + the new range).
324
+ // A pure addition (`+`) touches only the new range (1 slot). A pure deletion
325
+ // touches the old range(s). To stay budget-exact against the header sum we
326
+ // count: every non-`+` column contributes one old slot; the new column
327
+ // contributes one slot unless the line is a pure deletion in ALL columns.
328
+ function countSlots(prefix, fenceWidth) {
329
+ let slots = 0;
330
+ let anyAdd = false;
331
+ let anyNonAdd = false;
332
+ for (let i = 0; i < fenceWidth; i += 1) {
333
+ const c = prefix[i];
334
+ if (c === "+") {
335
+ anyAdd = true;
336
+ continue;
337
+ }
338
+ // space or `-` consumes that parent's old slot.
339
+ slots += 1;
340
+ anyNonAdd = true;
341
+ }
342
+ // The new range gains a line unless this is a pure deletion (every column
343
+ // is `-`, no space, no `+`) — a deletion removes from old, adds nothing new.
344
+ const pureDeletion = !anyAdd && !prefixHasSpace(prefix, fenceWidth);
345
+ if (!pureDeletion)
346
+ slots += 1;
347
+ // `anyNonAdd` guards the all-`+` combined case from under-counting the new
348
+ // slot (handled above: pure add → slots stays at the +1 for the new range).
349
+ void anyNonAdd;
350
+ return slots;
351
+ }
352
+ function prefixHasSpace(prefix, fenceWidth) {
353
+ for (let i = 0; i < fenceWidth; i += 1)
354
+ if (prefix[i] === " ")
355
+ return true;
356
+ return false;
357
+ }
358
+ // Git extended-header lines that live inside a `diff --git` section before the
359
+ // hunks. Recognized so they extend the section span and never get mistaken for
360
+ // content or garbage.
361
+ function isExtendedHeader(line) {
362
+ return /^(old mode|new mode|deleted file mode|new file mode|copy from|copy to|index |similarity index|dissimilarity index|mode )/.test(line);
363
+ }
364
+ // Recover old/new paths from a `diff --git a/X b/Y` line. Paths may contain
365
+ // spaces, which makes the split ambiguous; git's convention is `a/` then `b/`,
366
+ // so split on the ` b/` boundary when both carry the conventional prefixes,
367
+ // else fall back to the last-space split.
368
+ function parseGitPaths(rest) {
369
+ const r = rest.trim();
370
+ // Quoted form: `"a/x y" "b/z"`.
371
+ const quoted = /^"(.+)"\s+"(.+)"$/.exec(r);
372
+ if (quoted)
373
+ return { old: cleanPath(`"${quoted[1]}"`), new: cleanPath(`"${quoted[2]}"`) };
374
+ // Common form with a/ b/ prefixes; split on the ` b/` boundary.
375
+ const bIdx = r.indexOf(" b/");
376
+ if (r.startsWith("a/") && bIdx > 0) {
377
+ return { old: cleanPath(r.slice(0, bIdx)), new: cleanPath(r.slice(bIdx + 1)) };
378
+ }
379
+ // Fallback: midpoint split (identical paths are the common case here).
380
+ const parts = r.split(" ");
381
+ if (parts.length === 2)
382
+ return { old: cleanPath(parts[0]), new: cleanPath(parts[1]) };
383
+ const mid = parts.length / 2;
384
+ return {
385
+ old: cleanPath(parts.slice(0, mid).join(" ")),
386
+ new: cleanPath(parts.slice(mid).join(" ")),
387
+ };
388
+ }
389
+ //# sourceMappingURL=TextDiff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextDiff.js","sourceRoot":"","sources":["../src/TextDiff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { default as TextDiff } from "./TextDiff.ts";
2
+ export { default } from "./TextDiff.ts";
3
+ export { scanDiff } from "./TextDiff.ts";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as TextDiff } from "./TextDiff.js";
2
+ export { default } from "./TextDiff.js";
3
+ export { scanDiff } from "./TextDiff.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@plurnk/plurnk-mimetypes-text-diff",
3
+ "version": "0.1.0",
4
+ "description": "text/x-diff (unified/combined diff) mimetype handler for plurnk-service. Hand-rolled line-budget scanner; no parser dependency.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "engines": {
11
+ "node": ">=25"
12
+ },
13
+ "plurnk": {
14
+ "kind": "mimetype",
15
+ "handlers": [
16
+ {
17
+ "name": "text/x-diff",
18
+ "glyph": "🔀",
19
+ "extensions": [
20
+ ".diff",
21
+ ".patch"
22
+ ]
23
+ }
24
+ ]
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "dist/**/*",
35
+ "README.md"
36
+ ],
37
+ "scripts": {
38
+ "test:lint": "tsc --noEmit",
39
+ "test:unit": "node --test src/**/*.test.ts",
40
+ "test": "npm run test:lint && npm run test:unit",
41
+ "build:dist": "tsc -p tsconfig.build.json",
42
+ "build": "npm run build:dist",
43
+ "prepare": "npm run build"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^25.8.0",
47
+ "typescript": "^6.0.3",
48
+ "@plurnk/plurnk-mimetypes": "^0.15.0"
49
+ },
50
+ "peerDependencies": {
51
+ "@plurnk/plurnk-mimetypes": "^0.15.0"
52
+ }
53
+ }