@agentshouse/mdmodel 0.1.0-alpha.3 → 0.1.0-alpha.4
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/README.md +36 -3
- package/dist/document.d.ts +2 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +71 -1
- package/dist/document.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/links.d.ts +7 -1
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +259 -48
- package/dist/links.js.map +1 -1
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +5 -0
- package/dist/paths.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate.js +3 -3
- package/dist/validate.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/yaml.d.ts +1 -0
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +5 -3
- package/dist/yaml.js.map +1 -1
- package/fixtures/README.md +4 -0
- package/fixtures/cases/bracketed-link-definition/context.json +1 -0
- package/fixtures/cases/bracketed-link-definition/expected.json +1 -0
- package/fixtures/cases/bracketed-link-definition/library/item.md +10 -0
- package/fixtures/cases/bracketed-link-definition/library/types/task.md +7 -0
- package/fixtures/cases/duplicate-link-definition/context.json +1 -0
- package/fixtures/cases/duplicate-link-definition/expected.json +1 -0
- package/fixtures/cases/duplicate-link-definition/library/item.md +11 -0
- package/fixtures/cases/duplicate-link-definition/library/types/task.md +7 -0
- package/fixtures/cases/fenced-link-definition/context.json +1 -0
- package/fixtures/cases/fenced-link-definition/expected.json +1 -0
- package/fixtures/cases/fenced-link-definition/library/item.md +12 -0
- package/fixtures/cases/fenced-link-definition/library/types/task.md +7 -0
- package/fixtures/cases/interrupted-link-definition/context.json +1 -0
- package/fixtures/cases/interrupted-link-definition/expected.json +1 -0
- package/fixtures/cases/interrupted-link-definition/library/notes/draft.md +8 -0
- package/fixtures/cases/interrupted-link-definition/library/types/note.md +3 -0
- package/fixtures/cases/long-fence-link-definition/context.json +1 -0
- package/fixtures/cases/long-fence-link-definition/expected.json +1 -0
- package/fixtures/cases/long-fence-link-definition/library/notes/draft.md +9 -0
- package/fixtures/cases/long-fence-link-definition/library/types/note.md +3 -0
- package/fixtures/cases/paragraph-link-definition/context.json +5 -0
- package/fixtures/cases/paragraph-link-definition/expected.json +1 -0
- package/fixtures/cases/paragraph-link-definition/library/notes/draft.md +10 -0
- package/fixtures/cases/paragraph-link-definition/library/types/note.md +3 -0
- package/fixtures/cases/quoted-link-definition/context.json +1 -0
- package/fixtures/cases/quoted-link-definition/expected.json +6 -0
- package/fixtures/cases/quoted-link-definition/library/notes/draft.md +7 -0
- package/fixtures/cases/quoted-link-definition/library/types/note.md +3 -0
- package/fixtures/cases/truncated-link-definition/context.json +1 -0
- package/fixtures/cases/truncated-link-definition/expected.json +6 -0
- package/fixtures/cases/truncated-link-definition/library/notes/draft.md +7 -0
- package/fixtures/cases/truncated-link-definition/library/notes/other.md +5 -0
- package/fixtures/cases/truncated-link-definition/library/types/note.md +3 -0
- package/fixtures/cases/wide-item-fence-link/context.json +1 -0
- package/fixtures/cases/wide-item-fence-link/expected.json +1 -0
- package/fixtures/cases/wide-item-fence-link/library/notes/draft.md +7 -0
- package/fixtures/cases/wide-item-fence-link/library/types/note.md +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Package releases are published after the User confirms.
|
|
|
23
23
|
```ts
|
|
24
24
|
import {
|
|
25
25
|
apply,
|
|
26
|
+
links,
|
|
26
27
|
MARKDOWN_SYNTAX_PROFILE,
|
|
27
28
|
parse,
|
|
28
29
|
serialize,
|
|
@@ -47,6 +48,15 @@ if (parsed.ok) {
|
|
|
47
48
|
prior: 'Hello',
|
|
48
49
|
});
|
|
49
50
|
if (next.outcome === 'applied') serialize(next.document);
|
|
51
|
+
for (const link of links(parsed.document, 'tasks/two.md')) {
|
|
52
|
+
if (link.target !== 'data/export.csv') continue;
|
|
53
|
+
const moved = apply(parsed.document, {
|
|
54
|
+
kind: 'link',
|
|
55
|
+
href: link.href,
|
|
56
|
+
to: '../files/export.csv',
|
|
57
|
+
});
|
|
58
|
+
if (moved.outcome === 'applied') serialize(moved.document);
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
61
|
```
|
|
52
62
|
|
|
@@ -65,9 +75,32 @@ section, the violated rule, and `VERSION`. Every caller operation that validates
|
|
|
65
75
|
a resulting collection supplies that collection's context to this same
|
|
66
76
|
`validate` function. `parse`, `apply`, and `serialize` operate on a single text
|
|
67
77
|
Document; they do not validate collection membership or consume binary bytes.
|
|
68
|
-
`apply` sets one typed field, including a list field as a whole,
|
|
69
|
-
one declared section body against that value's prior content
|
|
70
|
-
does not reformat the rest of the file.
|
|
78
|
+
`apply` sets one typed field, including a list field as a whole, replaces
|
|
79
|
+
one declared section body against that value's prior content, or rewrites one
|
|
80
|
+
Markdown link destination. A field change does not reformat the rest of the file.
|
|
81
|
+
|
|
82
|
+
`links(document, file)` is the inventory of that Document's ordinary link and
|
|
83
|
+
image destinations that resolve to a collection Path, each with the destination
|
|
84
|
+
`href` exactly as written and the `target` Path it resolves to relative to
|
|
85
|
+
`file`. A `?query` or `#fragment` the destination carries is part of `href` and
|
|
86
|
+
not part of `target`. Entries are ordered by the position of the destination
|
|
87
|
+
text a rewrite would change, and one destination written the same way appears
|
|
88
|
+
once however many times it occurs. A destination that the syntax profile does not read as a
|
|
89
|
+
link, one that is empty, a bare fragment, or carries a URL scheme, and one that
|
|
90
|
+
leaves the collection are not in the inventory. Frontmatter field values,
|
|
91
|
+
including typed references, are not link destinations and are never enumerated.
|
|
92
|
+
|
|
93
|
+
`apply(document, { kind: 'link', href, to })` rewrites every body occurrence of
|
|
94
|
+
that written destination and changes nothing else, so a caller that moves a Path
|
|
95
|
+
rewrites its referring Documents without a second Markdown parser. `to` is the
|
|
96
|
+
Path the destination must resolve to instead; the `?query` or `#fragment` the
|
|
97
|
+
written destination carried is not part of that Path and is kept. A Document
|
|
98
|
+
that does not carry the destination is `stale`. A reference definition is
|
|
99
|
+
rewritten once however many uses name it. The destination is
|
|
100
|
+
written in the form the syntax profile reads back: bare when it carries no
|
|
101
|
+
whitespace, does not open with `<`, and balances its parentheses, otherwise
|
|
102
|
+
inside angle brackets, which the original form is kept in when it still fits. A
|
|
103
|
+
destination that neither form can carry is refused as `unwritable-link`.
|
|
71
104
|
|
|
72
105
|
## Fixtures
|
|
73
106
|
|
package/dist/document.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ApplyResult, Change, Document, ParseResult } from './types.js';
|
|
1
|
+
import type { ApplyResult, Change, Document, Link, ParseResult } from './types.js';
|
|
2
2
|
export declare function parse(content: string, file?: string): ParseResult;
|
|
3
|
+
export declare function links(document: Document, file: string): readonly Link[];
|
|
3
4
|
export declare function apply(document: Document, change: Change): ApplyResult;
|
|
4
5
|
export declare function serialize(document: Document): string;
|
|
5
6
|
//# sourceMappingURL=document.d.ts.map
|
package/dist/document.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoDnF,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,WAAW,CA0C7D;AAiGD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAWvE;AAED,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAYrE;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEpD"}
|
package/dist/document.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { hrefOccurrences } from './links.js';
|
|
2
|
+
import { classifyHref, hrefSuffix } from './paths.js';
|
|
1
3
|
import { headingSpans, sectionsRecord } from './sections.js';
|
|
2
4
|
import { refusal } from './refuse.js';
|
|
3
|
-
import { bodyStartAfterYaml, equalValue, extractFrontmatter, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError } from './yaml.js';
|
|
5
|
+
import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError } from './yaml.js';
|
|
4
6
|
const internals = new WeakMap();
|
|
5
7
|
function refuse(file, rule, location) {
|
|
6
8
|
return location === undefined ? refusal(file, rule) : refusal(file, rule, {
|
|
@@ -133,7 +135,75 @@ function applySection(document, name, value, prior) {
|
|
|
133
135
|
document: parsed.document
|
|
134
136
|
};
|
|
135
137
|
}
|
|
138
|
+
function fitsBare(to) {
|
|
139
|
+
if (to === '' || to.startsWith('<')) return false;
|
|
140
|
+
let depth = 0;
|
|
141
|
+
for (const character of to){
|
|
142
|
+
if (/[\s\u0000-\u001f\u007f]/.test(character)) return false;
|
|
143
|
+
if (character === '(') depth += 1;
|
|
144
|
+
else if (character === ')') {
|
|
145
|
+
if (depth === 0) return false;
|
|
146
|
+
depth -= 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return depth === 0;
|
|
150
|
+
}
|
|
151
|
+
function fitsBrackets(to) {
|
|
152
|
+
return to !== '' && !/[<>\n\r]/.test(to);
|
|
153
|
+
}
|
|
154
|
+
function destinationText(to, bracketed) {
|
|
155
|
+
if (fitsBare(to)) return bracketed && fitsBrackets(to) ? `<${to}>` : to;
|
|
156
|
+
if (fitsBrackets(to)) return `<${to}>`;
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function applyLink(document, href, to) {
|
|
160
|
+
const content = document.content;
|
|
161
|
+
const bodyStart = bodyStartOf(content);
|
|
162
|
+
const matches = hrefOccurrences(content.slice(bodyStart)).filter((occurrence)=>occurrence.href === href);
|
|
163
|
+
if (matches.length === 0) return {
|
|
164
|
+
outcome: 'stale',
|
|
165
|
+
document
|
|
166
|
+
};
|
|
167
|
+
const written = to + hrefSuffix(href);
|
|
168
|
+
let rewritten = '';
|
|
169
|
+
let cursor = 0;
|
|
170
|
+
for (const match of matches){
|
|
171
|
+
const text = destinationText(written, match.bracketed);
|
|
172
|
+
if (text === null) return {
|
|
173
|
+
outcome: 'refuse',
|
|
174
|
+
refusal: refuse('', 'unwritable-link', written)
|
|
175
|
+
};
|
|
176
|
+
rewritten += content.slice(cursor, bodyStart + match.start);
|
|
177
|
+
rewritten += text;
|
|
178
|
+
cursor = bodyStart + match.end;
|
|
179
|
+
}
|
|
180
|
+
const parsed = parse(rewritten + content.slice(cursor));
|
|
181
|
+
if (!parsed.ok) return {
|
|
182
|
+
outcome: 'refuse',
|
|
183
|
+
refusal: parsed.refusal
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
outcome: 'applied',
|
|
187
|
+
document: parsed.document
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function links(document, file) {
|
|
191
|
+
const found = [];
|
|
192
|
+
const seen = new Set();
|
|
193
|
+
for (const occurrence of hrefOccurrences(document.content.slice(bodyStartOf(document.content)))){
|
|
194
|
+
if (seen.has(occurrence.href)) continue;
|
|
195
|
+
const resolved = classifyHref(file, occurrence.href);
|
|
196
|
+
if (resolved.kind !== 'path') continue;
|
|
197
|
+
seen.add(occurrence.href);
|
|
198
|
+
found.push({
|
|
199
|
+
href: occurrence.href,
|
|
200
|
+
target: resolved.path
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return found;
|
|
204
|
+
}
|
|
136
205
|
export function apply(document, change) {
|
|
206
|
+
if (change.kind === 'link') return applyLink(document, change.href, change.to);
|
|
137
207
|
try {
|
|
138
208
|
if (change.kind === 'field') return applyField(document, change.name, change.value, change.prior);
|
|
139
209
|
return applySection(document, change.name, change.value, change.prior);
|
package/dist/document.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/workspace/packages/mdmodel/src/document.ts"],"sourcesContent":["import { headingSpans, sectionsRecord } from './sections.js';\nimport { refusal } from './refuse.js';\nimport type { ApplyResult, Change, Document, ParseResult } from './types.js';\nimport {\n bodyStartAfterYaml,\n equalValue,\n extractFrontmatter,\n jsValue,\n lineEnding,\n mapEntry,\n parseYamlMapping,\n serializeJs,\n serializeKey,\n YamlError,\n type YamlMap,\n} from './yaml.js';\n\ntype Internal = {\n readonly root: YamlMap | null;\n readonly yamlEnd: number;\n readonly bodyStart: number;\n};\n\nconst internals = new WeakMap<Document, Internal>();\n\nfunction refuse(file: string, rule: string, location?: string) {\n return location === undefined ? refusal(file, rule) : refusal(file, rule, { location });\n}\n\nfunction freezeDocument(\n content: string,\n type: string | undefined,\n fields: Record<string, unknown>,\n sections: Record<string, string>,\n internal: Internal,\n): Document {\n const document: Document = Object.freeze({\n content,\n type,\n fields: Object.freeze(fields),\n sections: Object.freeze(sections),\n });\n internals.set(document, internal);\n return document;\n}\n\nfunction declaredType(fields: Record<string, unknown>): string | undefined {\n const value = fields.type;\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string' || value === '') return undefined;\n return value;\n}\n\nexport function parse(content: string, file = ''): ParseResult {\n const fence = extractFrontmatter(content);\n if (fence.kind === 'none') {\n const spans = headingSpans(content, 0);\n return {\n ok: true,\n document: freezeDocument(content, undefined, {}, sectionsRecord(spans, content), {\n root: null,\n yamlEnd: 0,\n bodyStart: 0,\n }),\n };\n }\n if (fence.kind === 'unclosed') return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n try {\n const root = parseYamlMapping(content, fence.yamlStart, fence.yamlEnd);\n const fields = jsValue(root);\n if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {\n return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n }\n const bodyStart = bodyStartAfterYaml(content, fence.yamlEnd);\n const spans = headingSpans(content, bodyStart);\n const record = fields as Record<string, unknown>;\n return {\n ok: true,\n document: freezeDocument(\n content,\n declaredType(record),\n record,\n sectionsRecord(spans, content),\n {\n root,\n yamlEnd: fence.yamlEnd,\n bodyStart,\n },\n ),\n };\n } catch (error) {\n if (error instanceof YamlError)\n return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n throw error;\n }\n}\n\nfunction splice(content: string, start: number, end: number, insert: string): string {\n return content.slice(0, start) + insert + content.slice(end);\n}\n\nfunction applyField(document: Document, name: string, value: unknown, prior: unknown): ApplyResult {\n const internal = internals.get(document);\n if (internal === undefined || internal.root === null) {\n return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', name) };\n }\n if (!equalValue(document.fields[name], prior)) {\n return { outcome: 'stale', document };\n }\n let nextContent: string;\n const existing = mapEntry(internal.root, name);\n if (existing !== undefined) {\n nextContent = splice(\n document.content,\n existing.value.start,\n existing.value.end,\n serializeJs(value),\n );\n } else {\n const nl = lineEnding(document.content);\n const line = `${serializeKey(name)}: ${serializeJs(value)}${nl}`;\n const before = document.content.slice(0, internal.yamlEnd);\n const prefix = before.endsWith('\\n') || before.endsWith('\\r\\n') ? '' : nl;\n nextContent = splice(document.content, internal.yamlEnd, internal.yamlEnd, `${prefix}${line}`);\n }\n const parsed = parse(nextContent);\n if (!parsed.ok) return { outcome: 'refuse', refusal: parsed.refusal };\n return { outcome: 'applied', document: parsed.document };\n}\n\nfunction applySection(document: Document, name: string, value: string, prior: string): ApplyResult {\n const internal = internals.get(document);\n if (internal === undefined)\n return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };\n const spans = headingSpans(document.content, internal.bodyStart);\n const span = spans.find((item) => item.name === name);\n if (span === undefined)\n return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };\n const current = document.content.slice(span.bodyStart, span.bodyEnd);\n if (current !== prior) return { outcome: 'stale', document };\n const parsed = parse(splice(document.content, span.bodyStart, span.bodyEnd, value));\n if (!parsed.ok) return { outcome: 'refuse', refusal: parsed.refusal };\n return { outcome: 'applied', document: parsed.document };\n}\n\nexport function apply(document: Document, change: Change): ApplyResult {\n try {\n if (change.kind === 'field')\n return applyField(document, change.name, change.value, change.prior);\n return applySection(document, change.name, change.value, change.prior);\n } catch (error) {\n if (error instanceof YamlError) {\n return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', change.name) };\n }\n throw error;\n }\n}\n\nexport function serialize(document: Document): string {\n return document.content;\n}\n"],"names":["headingSpans","sectionsRecord","refusal","bodyStartAfterYaml","equalValue","extractFrontmatter","jsValue","lineEnding","mapEntry","parseYamlMapping","serializeJs","serializeKey","YamlError","internals","WeakMap","refuse","file","rule","location","undefined","freezeDocument","content","type","fields","sections","internal","document","Object","freeze","set","declaredType","value","parse","fence","kind","spans","ok","root","yamlEnd","bodyStart","yamlStart","Array","isArray","record","error","splice","start","end","insert","slice","applyField","name","prior","get","outcome","nextContent","existing","nl","line","before","prefix","endsWith","parsed","applySection","span","find","item","current","bodyEnd","apply","change","serialize"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/workspace/packages/mdmodel/src/document.ts"],"sourcesContent":["import { hrefOccurrences } from './links.js';\nimport { classifyHref, hrefSuffix } from './paths.js';\nimport { headingSpans, sectionsRecord } from './sections.js';\nimport { refusal } from './refuse.js';\nimport type { ApplyResult, Change, Document, Link, ParseResult } from './types.js';\nimport {\n bodyStartAfterYaml,\n bodyStartOf,\n equalValue,\n extractFrontmatter,\n jsValue,\n lineEnding,\n mapEntry,\n parseYamlMapping,\n serializeJs,\n serializeKey,\n YamlError,\n type YamlMap,\n} from './yaml.js';\n\ntype Internal = {\n readonly root: YamlMap | null;\n readonly yamlEnd: number;\n readonly bodyStart: number;\n};\n\nconst internals = new WeakMap<Document, Internal>();\n\nfunction refuse(file: string, rule: string, location?: string) {\n return location === undefined ? refusal(file, rule) : refusal(file, rule, { location });\n}\n\nfunction freezeDocument(\n content: string,\n type: string | undefined,\n fields: Record<string, unknown>,\n sections: Record<string, string>,\n internal: Internal,\n): Document {\n const document: Document = Object.freeze({\n content,\n type,\n fields: Object.freeze(fields),\n sections: Object.freeze(sections),\n });\n internals.set(document, internal);\n return document;\n}\n\nfunction declaredType(fields: Record<string, unknown>): string | undefined {\n const value = fields.type;\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string' || value === '') return undefined;\n return value;\n}\n\nexport function parse(content: string, file = ''): ParseResult {\n const fence = extractFrontmatter(content);\n if (fence.kind === 'none') {\n const spans = headingSpans(content, 0);\n return {\n ok: true,\n document: freezeDocument(content, undefined, {}, sectionsRecord(spans, content), {\n root: null,\n yamlEnd: 0,\n bodyStart: 0,\n }),\n };\n }\n if (fence.kind === 'unclosed') return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n try {\n const root = parseYamlMapping(content, fence.yamlStart, fence.yamlEnd);\n const fields = jsValue(root);\n if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {\n return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n }\n const bodyStart = bodyStartAfterYaml(content, fence.yamlEnd);\n const spans = headingSpans(content, bodyStart);\n const record = fields as Record<string, unknown>;\n return {\n ok: true,\n document: freezeDocument(\n content,\n declaredType(record),\n record,\n sectionsRecord(spans, content),\n {\n root,\n yamlEnd: fence.yamlEnd,\n bodyStart,\n },\n ),\n };\n } catch (error) {\n if (error instanceof YamlError)\n return { ok: false, refusal: refuse(file, 'frontmatter-fence') };\n throw error;\n }\n}\n\nfunction splice(content: string, start: number, end: number, insert: string): string {\n return content.slice(0, start) + insert + content.slice(end);\n}\n\nfunction applyField(document: Document, name: string, value: unknown, prior: unknown): ApplyResult {\n const internal = internals.get(document);\n if (internal === undefined || internal.root === null) {\n return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', name) };\n }\n if (!equalValue(document.fields[name], prior)) {\n return { outcome: 'stale', document };\n }\n let nextContent: string;\n const existing = mapEntry(internal.root, name);\n if (existing !== undefined) {\n nextContent = splice(\n document.content,\n existing.value.start,\n existing.value.end,\n serializeJs(value),\n );\n } else {\n const nl = lineEnding(document.content);\n const line = `${serializeKey(name)}: ${serializeJs(value)}${nl}`;\n const before = document.content.slice(0, internal.yamlEnd);\n const prefix = before.endsWith('\\n') || before.endsWith('\\r\\n') ? '' : nl;\n nextContent = splice(document.content, internal.yamlEnd, internal.yamlEnd, `${prefix}${line}`);\n }\n const parsed = parse(nextContent);\n if (!parsed.ok) return { outcome: 'refuse', refusal: parsed.refusal };\n return { outcome: 'applied', document: parsed.document };\n}\n\nfunction applySection(document: Document, name: string, value: string, prior: string): ApplyResult {\n const internal = internals.get(document);\n if (internal === undefined)\n return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };\n const spans = headingSpans(document.content, internal.bodyStart);\n const span = spans.find((item) => item.name === name);\n if (span === undefined)\n return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };\n const current = document.content.slice(span.bodyStart, span.bodyEnd);\n if (current !== prior) return { outcome: 'stale', document };\n const parsed = parse(splice(document.content, span.bodyStart, span.bodyEnd, value));\n if (!parsed.ok) return { outcome: 'refuse', refusal: parsed.refusal };\n return { outcome: 'applied', document: parsed.document };\n}\n\nfunction fitsBare(to: string): boolean {\n if (to === '' || to.startsWith('<')) return false;\n let depth = 0;\n for (const character of to) {\n if (/[\\s\\u0000-\\u001f\\u007f]/.test(character)) return false;\n if (character === '(') depth += 1;\n else if (character === ')') {\n if (depth === 0) return false;\n depth -= 1;\n }\n }\n return depth === 0;\n}\n\nfunction fitsBrackets(to: string): boolean {\n return to !== '' && !/[<>\\n\\r]/.test(to);\n}\n\nfunction destinationText(to: string, bracketed: boolean): string | null {\n if (fitsBare(to)) return bracketed && fitsBrackets(to) ? `<${to}>` : to;\n if (fitsBrackets(to)) return `<${to}>`;\n return null;\n}\n\nfunction applyLink(document: Document, href: string, to: string): ApplyResult {\n const content = document.content;\n const bodyStart = bodyStartOf(content);\n const matches = hrefOccurrences(content.slice(bodyStart)).filter(\n (occurrence) => occurrence.href === href,\n );\n if (matches.length === 0) return { outcome: 'stale', document };\n const written = to + hrefSuffix(href);\n let rewritten = '';\n let cursor = 0;\n for (const match of matches) {\n const text = destinationText(written, match.bracketed);\n if (text === null)\n return { outcome: 'refuse', refusal: refuse('', 'unwritable-link', written) };\n rewritten += content.slice(cursor, bodyStart + match.start);\n rewritten += text;\n cursor = bodyStart + match.end;\n }\n const parsed = parse(rewritten + content.slice(cursor));\n if (!parsed.ok) return { outcome: 'refuse', refusal: parsed.refusal };\n return { outcome: 'applied', document: parsed.document };\n}\n\nexport function links(document: Document, file: string): readonly Link[] {\n const found: Link[] = [];\n const seen = new Set<string>();\n for (const occurrence of hrefOccurrences(document.content.slice(bodyStartOf(document.content)))) {\n if (seen.has(occurrence.href)) continue;\n const resolved = classifyHref(file, occurrence.href);\n if (resolved.kind !== 'path') continue;\n seen.add(occurrence.href);\n found.push({ href: occurrence.href, target: resolved.path });\n }\n return found;\n}\n\nexport function apply(document: Document, change: Change): ApplyResult {\n if (change.kind === 'link') return applyLink(document, change.href, change.to);\n try {\n if (change.kind === 'field')\n return applyField(document, change.name, change.value, change.prior);\n return applySection(document, change.name, change.value, change.prior);\n } catch (error) {\n if (error instanceof YamlError) {\n return { outcome: 'refuse', refusal: refuse('', 'frontmatter-fence', change.name) };\n }\n throw error;\n }\n}\n\nexport function serialize(document: Document): string {\n return document.content;\n}\n"],"names":["hrefOccurrences","classifyHref","hrefSuffix","headingSpans","sectionsRecord","refusal","bodyStartAfterYaml","bodyStartOf","equalValue","extractFrontmatter","jsValue","lineEnding","mapEntry","parseYamlMapping","serializeJs","serializeKey","YamlError","internals","WeakMap","refuse","file","rule","location","undefined","freezeDocument","content","type","fields","sections","internal","document","Object","freeze","set","declaredType","value","parse","fence","kind","spans","ok","root","yamlEnd","bodyStart","yamlStart","Array","isArray","record","error","splice","start","end","insert","slice","applyField","name","prior","get","outcome","nextContent","existing","nl","line","before","prefix","endsWith","parsed","applySection","span","find","item","current","bodyEnd","fitsBare","to","startsWith","depth","character","test","fitsBrackets","destinationText","bracketed","applyLink","href","matches","filter","occurrence","length","written","rewritten","cursor","match","text","links","found","seen","Set","has","resolved","add","push","target","path","apply","change","serialize"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { apply, parse, serialize } from './document.js';
|
|
1
|
+
export { apply, links, parse, serialize } from './document.js';
|
|
2
2
|
export { MARKDOWN_SYNTAX_PROFILE } from './profile.js';
|
|
3
|
-
export type { ApplyResult, Change, Document, Library, ParseResult, Refusal, Validation, ValidationContext, } from './types.js';
|
|
3
|
+
export type { ApplyResult, Change, Document, Library, Link, ParseResult, Refusal, Validation, ValidationContext, } from './types.js';
|
|
4
4
|
export { validate } from './validate.js';
|
|
5
5
|
export { VERSION } from './version.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,YAAY,EACV,WAAW,EACX,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,EACP,UAAU,EACV,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/workspace/packages/mdmodel/src/index.ts"],"sourcesContent":["export { apply, parse, serialize } from './document.js';\nexport { MARKDOWN_SYNTAX_PROFILE } from './profile.js';\nexport type {\n ApplyResult,\n Change,\n Document,\n Library,\n ParseResult,\n Refusal,\n Validation,\n ValidationContext,\n} from './types.js';\nexport { validate } from './validate.js';\nexport { VERSION } from './version.js';\n"],"names":["apply","parse","serialize","MARKDOWN_SYNTAX_PROFILE","validate","VERSION"],"mappings":"AAAA,SAASA,KAAK,EAAEC,KAAK,EAAEC,SAAS,QAAQ,gBAAgB;
|
|
1
|
+
{"version":3,"sources":["/workspace/packages/mdmodel/src/index.ts"],"sourcesContent":["export { apply, links, parse, serialize } from './document.js';\nexport { MARKDOWN_SYNTAX_PROFILE } from './profile.js';\nexport type {\n ApplyResult,\n Change,\n Document,\n Library,\n Link,\n ParseResult,\n Refusal,\n Validation,\n ValidationContext,\n} from './types.js';\nexport { validate } from './validate.js';\nexport { VERSION } from './version.js';\n"],"names":["apply","links","parse","serialize","MARKDOWN_SYNTAX_PROFILE","validate","VERSION"],"mappings":"AAAA,SAASA,KAAK,EAAEC,KAAK,EAAEC,KAAK,EAAEC,SAAS,QAAQ,gBAAgB;AAC/D,SAASC,uBAAuB,QAAQ,eAAe;AAYvD,SAASC,QAAQ,QAAQ,gBAAgB;AACzC,SAASC,OAAO,QAAQ,eAAe"}
|
package/dist/links.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type HrefOccurrence = {
|
|
2
|
+
readonly href: string;
|
|
3
|
+
readonly start: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
readonly bracketed: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function hrefOccurrences(body: string): readonly HrefOccurrence[];
|
|
2
8
|
//# sourceMappingURL=links.d.ts.map
|
package/dist/links.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"AA4VA,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CA2BvE"}
|
package/dist/links.js
CHANGED
|
@@ -1,17 +1,127 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const TAB_STOP = 4;
|
|
2
|
+
const CLOSING_FENCE_INDENT = 3;
|
|
3
|
+
const HEADING = /^#{1,6}(?:[ \t]|$)/;
|
|
4
|
+
const BREAK = /^(?:(?:\*[ \t]*){3,}|(?:-[ \t]*){3,}|(?:_[ \t]*){3,})$/;
|
|
5
|
+
const SETEXT = /^(?:=+|-+)[ \t]*$/;
|
|
6
|
+
const LABEL = /^\[((?:[^\\\]\n]|\\.)+)\]:[ \t]*/;
|
|
7
|
+
const DESTINATION = /^(?:(<[^<>\n\r]*>)|([^\s<][^\s]*))/d;
|
|
8
|
+
const TITLE = /^(?:"[^"\n]*"|'[^'\n]*'|\([^()\n]*\))[ \t]*$/;
|
|
9
|
+
function endOfLine(text, from) {
|
|
10
|
+
const nl = text.indexOf('\n', from);
|
|
11
|
+
return nl === -1 ? text.length : nl;
|
|
5
12
|
}
|
|
6
|
-
function
|
|
13
|
+
function afterLine(text, end) {
|
|
14
|
+
return end < text.length ? end + 1 : text.length;
|
|
15
|
+
}
|
|
16
|
+
function itemMarker(text, index, end) {
|
|
7
17
|
let i = index;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const bullet = text[i];
|
|
19
|
+
if (bullet === '-' || bullet === '*' || bullet === '+') i += 1;
|
|
20
|
+
else {
|
|
21
|
+
let digits = 0;
|
|
22
|
+
while(digits < 9 && i + digits < end && text[i + digits] >= '0' && text[i + digits] <= '9')digits += 1;
|
|
23
|
+
const delimiter = text[i + digits];
|
|
24
|
+
if (digits === 0 || delimiter !== '.' && delimiter !== ')') return null;
|
|
25
|
+
i += digits + 1;
|
|
26
|
+
}
|
|
27
|
+
if (i >= end) return i;
|
|
28
|
+
if (text[i] !== ' ' && text[i] !== '\t') return null;
|
|
29
|
+
return i + 1;
|
|
30
|
+
}
|
|
31
|
+
function readLine(text, start, end) {
|
|
32
|
+
let i = start;
|
|
33
|
+
let depth = 0;
|
|
34
|
+
let item = false;
|
|
35
|
+
let inner = start;
|
|
36
|
+
for(;;){
|
|
37
|
+
let spaces = 0;
|
|
38
|
+
while(spaces < 3 && i < end && text[i] === ' '){
|
|
39
|
+
i += 1;
|
|
40
|
+
spaces += 1;
|
|
41
|
+
}
|
|
42
|
+
if (i < end && text[i] === '>') {
|
|
43
|
+
depth += 1;
|
|
44
|
+
i += 1;
|
|
45
|
+
if (i < end && text[i] === ' ') i += 1;
|
|
46
|
+
inner = i;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const after = i < end ? itemMarker(text, i, end) : null;
|
|
50
|
+
if (after === null) break;
|
|
51
|
+
item = true;
|
|
52
|
+
i = after;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
start,
|
|
56
|
+
end,
|
|
57
|
+
content: i,
|
|
58
|
+
depth,
|
|
59
|
+
item,
|
|
60
|
+
inner
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function widthOf(text, from, to) {
|
|
64
|
+
let width = 0;
|
|
65
|
+
for(let i = from; i < to; i += 1)width += text[i] === '\t' ? TAB_STOP - width % TAB_STOP : 1;
|
|
66
|
+
return width;
|
|
67
|
+
}
|
|
68
|
+
function afterIndent(text, line) {
|
|
69
|
+
let i = line.inner;
|
|
70
|
+
while(i < line.end && (text[i] === ' ' || text[i] === '\t'))i += 1;
|
|
71
|
+
return i;
|
|
72
|
+
}
|
|
73
|
+
function indentWidth(text, line) {
|
|
74
|
+
return widthOf(text, line.inner, afterIndent(text, line));
|
|
75
|
+
}
|
|
76
|
+
function lineText(text, line) {
|
|
77
|
+
const raw = text.slice(line.content, line.end);
|
|
78
|
+
return raw.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
79
|
+
}
|
|
80
|
+
function fenceRun(text, at, end) {
|
|
81
|
+
const char = text[at];
|
|
82
|
+
if (char !== '`' && char !== '~') return null;
|
|
83
|
+
let length = 0;
|
|
84
|
+
while(at + length < end && text[at + length] === char)length += 1;
|
|
85
|
+
return length < 3 ? null : {
|
|
86
|
+
char,
|
|
87
|
+
length
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function fenceAt(text, line) {
|
|
91
|
+
const run = fenceRun(text, line.content, line.end);
|
|
92
|
+
if (run === null) return null;
|
|
93
|
+
return {
|
|
94
|
+
char: run.char,
|
|
95
|
+
length: run.length,
|
|
96
|
+
depth: line.depth,
|
|
97
|
+
column: line.item ? widthOf(text, line.inner, line.content) : 0,
|
|
98
|
+
item: line.item
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function closesFence(text, line, open) {
|
|
102
|
+
if (line.depth !== open.depth || line.item) return false;
|
|
103
|
+
const indent = indentWidth(text, line);
|
|
104
|
+
if (indent < open.column || indent > open.column + CLOSING_FENCE_INDENT) return false;
|
|
105
|
+
const at = afterIndent(text, line);
|
|
106
|
+
const run = fenceRun(text, at, line.end);
|
|
107
|
+
if (run === null || run.char !== open.char || run.length < open.length) return false;
|
|
108
|
+
return text.slice(at + run.length, line.end).trim() === '';
|
|
109
|
+
}
|
|
110
|
+
function leavesContainer(text, line, open) {
|
|
111
|
+
if (line.depth < open.depth) return true;
|
|
112
|
+
if (!open.item || lineText(text, line).trim() === '') return false;
|
|
113
|
+
return indentWidth(text, line) < open.column;
|
|
114
|
+
}
|
|
115
|
+
function fenceSpan(text, line, open) {
|
|
116
|
+
let cursor = afterLine(text, line.end);
|
|
117
|
+
while(cursor < text.length){
|
|
118
|
+
const end = endOfLine(text, cursor);
|
|
119
|
+
const next = readLine(text, cursor, end);
|
|
120
|
+
if (leavesContainer(text, next, open)) return cursor;
|
|
121
|
+
if (closesFence(text, next, open)) return afterLine(text, end);
|
|
122
|
+
cursor = afterLine(text, end);
|
|
123
|
+
}
|
|
124
|
+
return text.length;
|
|
15
125
|
}
|
|
16
126
|
function skipInlineCode(text, index) {
|
|
17
127
|
let ticks = 0;
|
|
@@ -40,11 +150,16 @@ function skipTitle(text, start) {
|
|
|
40
150
|
}
|
|
41
151
|
function destination(text, start) {
|
|
42
152
|
let i = skipLinkSpace(text, start + 1);
|
|
43
|
-
let
|
|
153
|
+
let occurrence;
|
|
44
154
|
if (text[i] === '<') {
|
|
45
155
|
const close = text.indexOf('>', i + 1);
|
|
46
156
|
if (close === -1) return null;
|
|
47
|
-
|
|
157
|
+
occurrence = {
|
|
158
|
+
href: text.slice(i + 1, close),
|
|
159
|
+
start: i,
|
|
160
|
+
end: close + 1,
|
|
161
|
+
bracketed: true
|
|
162
|
+
};
|
|
48
163
|
i = close + 1;
|
|
49
164
|
} else {
|
|
50
165
|
const from = i;
|
|
@@ -59,7 +174,12 @@ function destination(text, start) {
|
|
|
59
174
|
}
|
|
60
175
|
i += 1;
|
|
61
176
|
}
|
|
62
|
-
|
|
177
|
+
occurrence = {
|
|
178
|
+
href: text.slice(from, i),
|
|
179
|
+
start: from,
|
|
180
|
+
end: i,
|
|
181
|
+
bracketed: false
|
|
182
|
+
};
|
|
63
183
|
}
|
|
64
184
|
i = skipLinkSpace(text, i);
|
|
65
185
|
const afterTitle = skipTitle(text, i);
|
|
@@ -67,76 +187,167 @@ function destination(text, start) {
|
|
|
67
187
|
i = skipLinkSpace(text, afterTitle);
|
|
68
188
|
if (text[i] !== ')') return null;
|
|
69
189
|
return {
|
|
70
|
-
|
|
190
|
+
occurrence,
|
|
71
191
|
end: i + 1
|
|
72
192
|
};
|
|
73
193
|
}
|
|
74
|
-
function
|
|
75
|
-
let i =
|
|
194
|
+
function labelEnd(text, start) {
|
|
195
|
+
let i = start;
|
|
76
196
|
let depth = 1;
|
|
77
197
|
while(i < text.length && depth > 0){
|
|
78
198
|
const ch = text[i];
|
|
199
|
+
if (ch === '\\') {
|
|
200
|
+
i += 2;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
79
203
|
if (ch === '[') depth += 1;
|
|
80
204
|
else if (ch === ']') depth -= 1;
|
|
81
|
-
else if (ch === '\n' || ch === '\r') return
|
|
205
|
+
else if (ch === '\n' || ch === '\r') return -1;
|
|
82
206
|
i += 1;
|
|
83
207
|
}
|
|
84
|
-
|
|
85
|
-
|
|
208
|
+
return depth === 0 ? i : -1;
|
|
209
|
+
}
|
|
210
|
+
function takeLink(text, index, found, defs) {
|
|
211
|
+
const end = labelEnd(text, index + 1);
|
|
212
|
+
if (end === -1) return index + 1;
|
|
213
|
+
const label = text.slice(index + 1, end - 1);
|
|
214
|
+
let i = end;
|
|
86
215
|
if (text[i] === '(') {
|
|
87
216
|
const dest = destination(text, i);
|
|
88
217
|
if (dest === null) return index + 1;
|
|
89
|
-
|
|
218
|
+
found.push(dest.occurrence);
|
|
90
219
|
return dest.end;
|
|
91
220
|
}
|
|
92
221
|
if (text[i] === '[') {
|
|
93
|
-
const close = text
|
|
222
|
+
const close = labelEnd(text, i + 1);
|
|
94
223
|
if (close === -1) return index + 1;
|
|
95
|
-
const id = (text.slice(i + 1, close) || label).trim().toLowerCase();
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return close
|
|
224
|
+
const id = (text.slice(i + 1, close - 1) || label).trim().toLowerCase();
|
|
225
|
+
const defined = defs.get(id);
|
|
226
|
+
if (defined !== undefined) found.push(defined);
|
|
227
|
+
return close;
|
|
99
228
|
}
|
|
100
229
|
return i;
|
|
101
230
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
231
|
+
function definitionDestination(text, from, to) {
|
|
232
|
+
let i = from;
|
|
233
|
+
while(i < to && (text[i] === ' ' || text[i] === '\t'))i += 1;
|
|
234
|
+
const match = DESTINATION.exec(text.slice(i, to));
|
|
235
|
+
if (match === null) return null;
|
|
236
|
+
const bracketed = match[1];
|
|
237
|
+
const span = match.indices?.[1] ?? match.indices?.[2];
|
|
238
|
+
const href = bracketed === undefined ? match[2] : bracketed.slice(1, -1);
|
|
239
|
+
if (href === undefined || span === undefined) return null;
|
|
240
|
+
return {
|
|
241
|
+
href,
|
|
242
|
+
start: i + span[0],
|
|
243
|
+
end: i + span[1],
|
|
244
|
+
bracketed: bracketed !== undefined
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function definitionAt(text, line) {
|
|
248
|
+
const head = LABEL.exec(lineText(text, line));
|
|
249
|
+
if (head === null) return null;
|
|
250
|
+
const id = head[1]?.trim().toLowerCase();
|
|
251
|
+
if (id === undefined || id === '') return null;
|
|
252
|
+
const from = line.content + head[0].length;
|
|
253
|
+
let occurrence = definitionDestination(text, from, line.end);
|
|
254
|
+
let next = afterLine(text, line.end);
|
|
255
|
+
if (occurrence === null) {
|
|
256
|
+
if (text.slice(from, line.end).trim() !== '' || next >= text.length) return null;
|
|
257
|
+
const end = endOfLine(text, next);
|
|
258
|
+
occurrence = definitionDestination(text, readLine(text, next, end).content, end);
|
|
259
|
+
if (occurrence === null) return null;
|
|
260
|
+
next = afterLine(text, end);
|
|
261
|
+
}
|
|
262
|
+
if (next < text.length) {
|
|
263
|
+
const end = endOfLine(text, next);
|
|
264
|
+
if (TITLE.test(lineText(text, readLine(text, next, end)))) next = afterLine(text, end);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
id,
|
|
268
|
+
occurrence,
|
|
269
|
+
next
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function endsBlock(content) {
|
|
273
|
+
return HEADING.test(content) || BREAK.test(content) || SETEXT.test(content);
|
|
274
|
+
}
|
|
275
|
+
function scanBlocks(text) {
|
|
276
|
+
const fences = [];
|
|
277
|
+
const definitions = new Map();
|
|
278
|
+
let paragraph = false;
|
|
279
|
+
let depth = 0;
|
|
280
|
+
let i = 0;
|
|
281
|
+
while(i < text.length){
|
|
282
|
+
const line = readLine(text, i, endOfLine(text, i));
|
|
283
|
+
if (line.depth !== depth || line.item) paragraph = false;
|
|
284
|
+
depth = line.depth;
|
|
285
|
+
const open = fenceAt(text, line);
|
|
286
|
+
if (open !== null) {
|
|
287
|
+
const stop = fenceSpan(text, line, open);
|
|
288
|
+
fences.push([
|
|
289
|
+
i,
|
|
290
|
+
stop
|
|
291
|
+
]);
|
|
292
|
+
paragraph = false;
|
|
293
|
+
i = stop;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const content = lineText(text, line);
|
|
297
|
+
if (content.trim() === '') {
|
|
298
|
+
paragraph = false;
|
|
299
|
+
i = afterLine(text, line.end);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const definition = paragraph ? null : definitionAt(text, line);
|
|
303
|
+
if (definition !== null) {
|
|
304
|
+
if (!definitions.has(definition.id)) definitions.set(definition.id, definition.occurrence);
|
|
305
|
+
i = definition.next;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
paragraph = !endsBlock(content);
|
|
309
|
+
i = afterLine(text, line.end);
|
|
109
310
|
}
|
|
110
|
-
return
|
|
311
|
+
return {
|
|
312
|
+
fences,
|
|
313
|
+
definitions
|
|
314
|
+
};
|
|
111
315
|
}
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
316
|
+
function fenceEndAt(spans, index) {
|
|
317
|
+
for (const [start, end] of spans){
|
|
318
|
+
if (index >= start && index < end) return end;
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
export function hrefOccurrences(body) {
|
|
323
|
+
const { fences, definitions } = scanBlocks(body);
|
|
324
|
+
const found = [];
|
|
115
325
|
let i = 0;
|
|
116
326
|
while(i < body.length){
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
i = skipFence(body, i);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
327
|
+
const fenceEnd = fenceEndAt(fences, i);
|
|
328
|
+
if (fenceEnd !== undefined) {
|
|
329
|
+
i = fenceEnd;
|
|
330
|
+
continue;
|
|
124
331
|
}
|
|
125
332
|
if (body[i] === '`') {
|
|
126
333
|
i = skipInlineCode(body, i);
|
|
127
334
|
continue;
|
|
128
335
|
}
|
|
129
336
|
if (body[i] === '!' && body[i + 1] === '[') {
|
|
130
|
-
i = takeLink(body, i + 1,
|
|
337
|
+
i = takeLink(body, i + 1, found, definitions);
|
|
131
338
|
continue;
|
|
132
339
|
}
|
|
133
340
|
if (body[i] === '[') {
|
|
134
|
-
i = takeLink(body, i,
|
|
341
|
+
i = takeLink(body, i, found, definitions);
|
|
135
342
|
continue;
|
|
136
343
|
}
|
|
137
344
|
i += 1;
|
|
138
345
|
}
|
|
139
|
-
|
|
346
|
+
const byStart = new Map();
|
|
347
|
+
for (const occurrence of found)byStart.set(occurrence.start, occurrence);
|
|
348
|
+
return [
|
|
349
|
+
...byStart.values()
|
|
350
|
+
].sort((left, right)=>left.start - right.start);
|
|
140
351
|
}
|
|
141
352
|
|
|
142
353
|
//# sourceMappingURL=links.js.map
|