dotmd-cli 0.76.7 → 0.76.8
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/package.json +1 -1
- package/src/extractors.mjs +3 -3
- package/src/markdown-code-spans.mjs +47 -0
- package/src/reference-planner.mjs +27 -47
package/package.json
CHANGED
package/src/extractors.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { maskInlineCodeSpans } from './markdown-code-spans.mjs';
|
|
2
|
+
|
|
1
3
|
export function extractFirstHeading(body) {
|
|
2
4
|
return body.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? null;
|
|
3
5
|
}
|
|
@@ -49,9 +51,7 @@ export function extractBodyLinks(body) {
|
|
|
49
51
|
// every one of them was also never checked for breakage. Masking to same-length
|
|
50
52
|
// filler keeps the link matchable while still neutralizing a link that is
|
|
51
53
|
// itself inside code (`[fake](x.md)` stays unmatched), and preserves offsets.
|
|
52
|
-
const stripped = body
|
|
53
|
-
.replace(/^```[\s\S]*?^```/gm, '')
|
|
54
|
-
.replace(/`[^`]+`/g, match => 'x'.repeat(match.length));
|
|
54
|
+
const stripped = maskInlineCodeSpans(body.replace(/^```[\s\S]*?^```/gm, ''));
|
|
55
55
|
const links = [];
|
|
56
56
|
// Supported inline destinations are a whitespace-free token (with Markdown
|
|
57
57
|
// backslash escapes) or an angle-bracket destination, plus an optional
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function maskRange(value, start, end) {
|
|
2
|
+
return value.slice(0, start)
|
|
3
|
+
+ value.slice(start, end).replace(/[^\n]/g, 'x')
|
|
4
|
+
+ value.slice(end);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Markdown code spans close only on a backtick run of the same length as their
|
|
8
|
+
// opener. The mask is deliberately the same length as the source so consumers
|
|
9
|
+
// can inspect neutralized Markdown while applying any edits at source offsets.
|
|
10
|
+
// `state` lets the conservative multiline behavior used by the reference
|
|
11
|
+
// rewriter survive: after an unmatched opener, nothing is treated as prose
|
|
12
|
+
// until a compatible closer appears.
|
|
13
|
+
export function maskInlineCodeLine(line, state = { run: null }) {
|
|
14
|
+
const ranges = [];
|
|
15
|
+
const runs = [...line.matchAll(/`+/g)];
|
|
16
|
+
let index = 0;
|
|
17
|
+
|
|
18
|
+
if (state.run !== null) {
|
|
19
|
+
const closingIndex = runs.findIndex(candidate => candidate[0].length === state.run);
|
|
20
|
+
if (closingIndex === -1) return maskRange(line, 0, line.length);
|
|
21
|
+
const closing = runs[closingIndex];
|
|
22
|
+
ranges.push([0, closing.index + closing[0].length]);
|
|
23
|
+
index = closingIndex + 1;
|
|
24
|
+
state.run = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (; index < runs.length; index++) {
|
|
28
|
+
const opening = runs[index];
|
|
29
|
+
const closingIndex = runs.findIndex((candidate, candidateIndex) =>
|
|
30
|
+
candidateIndex > index && candidate[0].length === opening[0].length);
|
|
31
|
+
if (closingIndex === -1) {
|
|
32
|
+
ranges.push([opening.index, line.length]);
|
|
33
|
+
state.run = opening[0].length;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
const closing = runs[closingIndex];
|
|
37
|
+
ranges.push([opening.index, closing.index + closing[0].length]);
|
|
38
|
+
index = closingIndex;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return ranges.reduceRight((masked, [start, end]) => maskRange(masked, start, end), line);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function maskInlineCodeSpans(value) {
|
|
45
|
+
const state = { run: null };
|
|
46
|
+
return value.split('\n').map(line => maskInlineCodeLine(line, state)).join('\n');
|
|
47
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { extractFrontmatter } from './frontmatter.mjs';
|
|
4
|
+
import { maskInlineCodeLine } from './markdown-code-spans.mjs';
|
|
4
5
|
|
|
5
6
|
function slash(value) { return value.split(path.sep).join('/'); }
|
|
6
7
|
|
|
@@ -233,29 +234,37 @@ function rewriteDestination(raw, args) {
|
|
|
233
234
|
return parsed.angle ? `<${rendered}>` : rendered;
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
237
|
+
function rewriteMarkdownLine(line, masked, args) {
|
|
238
|
+
const edits = [];
|
|
239
|
+
const inline = /(\[[^\]]*\]\(\s*)(<[^>\n]+>|(?:\\.|[^\s()])+)(\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?(\s*\))/g;
|
|
240
|
+
let match;
|
|
241
|
+
while ((match = inline.exec(masked)) !== null) {
|
|
242
|
+
let escapes = 0;
|
|
243
|
+
for (let index = match.index - 1; index >= 0 && line[index] === '\\'; index--) escapes++;
|
|
244
|
+
if (escapes % 2 === 1) continue;
|
|
245
|
+
const start = match.index + match[1].length;
|
|
246
|
+
const destination = line.slice(start, start + match[2].length);
|
|
247
|
+
const rewritten = rewriteDestination(destination, args);
|
|
248
|
+
if (rewritten !== destination) edits.push({ start, end: start + destination.length, value: rewritten });
|
|
249
|
+
}
|
|
250
|
+
|
|
246
251
|
// Reference definitions preserve labels, spacing, destinations, and titles.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
const definition = /^(\s{0,3}\[[^\]]+\]:\s*)(<[^>\n]+>|(?:\\.|[^\s])+)(.*)$/.exec(masked);
|
|
253
|
+
if (definition) {
|
|
254
|
+
const start = definition[1].length;
|
|
255
|
+
const destination = line.slice(start, start + definition[2].length);
|
|
256
|
+
const rewritten = rewriteDestination(destination, args);
|
|
257
|
+
if (rewritten !== destination) edits.push({ start, end: start + destination.length, value: rewritten });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return edits.sort((left, right) => right.start - left.start)
|
|
261
|
+
.reduce((next, edit) => next.slice(0, edit.start) + edit.value + next.slice(edit.end), line);
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
function rewriteMarkdown(body, args) {
|
|
256
265
|
const lines = body.split('\n');
|
|
257
266
|
let fence = null;
|
|
258
|
-
|
|
267
|
+
const inlineState = { run: null };
|
|
259
268
|
let indentedCode = false;
|
|
260
269
|
let htmlBlock = null;
|
|
261
270
|
const containerContext = line => {
|
|
@@ -317,36 +326,7 @@ function rewriteMarkdown(body, args) {
|
|
|
317
326
|
fence = { char: opener[1][0], length: opener[1].length, prefixes: container.prefixes };
|
|
318
327
|
return line;
|
|
319
328
|
}
|
|
320
|
-
|
|
321
|
-
let cursor = 0;
|
|
322
|
-
const runs = [...line.matchAll(/`+/g)];
|
|
323
|
-
let index = 0;
|
|
324
|
-
if (inlineRun !== null) {
|
|
325
|
-
const closingIndex = runs.findIndex(candidate => candidate[0].length === inlineRun);
|
|
326
|
-
if (closingIndex === -1) return line;
|
|
327
|
-
const closing = runs[closingIndex];
|
|
328
|
-
output += line.slice(0, closing.index + closing[0].length);
|
|
329
|
-
cursor = closing.index + closing[0].length;
|
|
330
|
-
index = closingIndex + 1;
|
|
331
|
-
inlineRun = null;
|
|
332
|
-
}
|
|
333
|
-
for (; index < runs.length; index++) {
|
|
334
|
-
const opening = runs[index];
|
|
335
|
-
const closingIndex = runs.findIndex((candidate, candidateIndex) => candidateIndex > index && candidate[0].length === opening[0].length);
|
|
336
|
-
if (closingIndex === -1) {
|
|
337
|
-
output += rewriteMarkdownSegment(line.slice(cursor, opening.index), args);
|
|
338
|
-
output += line.slice(opening.index);
|
|
339
|
-
inlineRun = opening[0].length;
|
|
340
|
-
cursor = line.length;
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
const closing = runs[closingIndex];
|
|
344
|
-
output += rewriteMarkdownSegment(line.slice(cursor, opening.index), args);
|
|
345
|
-
output += line.slice(opening.index, closing.index + closing[0].length);
|
|
346
|
-
cursor = closing.index + closing[0].length;
|
|
347
|
-
index = closingIndex;
|
|
348
|
-
}
|
|
349
|
-
return output + rewriteMarkdownSegment(line.slice(cursor), args);
|
|
329
|
+
return rewriteMarkdownLine(line, maskInlineCodeLine(line, inlineState), args);
|
|
350
330
|
}).join('\n');
|
|
351
331
|
}
|
|
352
332
|
|