@agentshouse/mdmodel 0.1.0-c64dbada0730d → 0.1.0-c669bdbad8a03
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/API.md +206 -202
- package/README.md +15 -18
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +7 -14
- package/dist/condition.d.ts +2 -1
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +30 -15
- package/dist/definition.d.ts +15 -0
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +226 -26
- package/dist/divide.d.ts.map +1 -1
- package/dist/divide.js +18 -8
- package/dist/document.d.ts +5 -2
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +315 -165
- package/dist/fields.d.ts +2 -0
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +7 -0
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/links.d.ts +2 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +23 -12
- package/dist/paths.d.ts +10 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +115 -12
- package/dist/project.d.ts +1 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +35 -10
- package/dist/references.d.ts +2 -2
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +6 -1
- package/dist/schema.js +2 -2
- package/dist/types.d.ts +44 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +8 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +205 -100
- package/dist/yaml.d.ts +7 -3
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +72 -43
- package/fixtures/README.md +1 -1
- package/fixtures/cases/cross-room-link/context.json +1 -0
- package/fixtures/cases/cross-room-link/expected.json +1 -0
- package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
- package/fixtures/cases/cross-room-link/schema.yaml +3 -0
- package/fixtures/cases/cross-room-reference/context.json +1 -0
- package/fixtures/cases/cross-room-reference/expected.json +1 -0
- package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
- package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
- package/package.json +4 -2
package/dist/document.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type Definition } from './definition.js';
|
|
2
|
-
import type { ApplyResult, BodyResult, Change, Document, Link, ParseResult } from './types.js';
|
|
2
|
+
import type { ApplyResult, BodyResult, FrontmatterResult, Change, Document, Link, LinkOccurrence, ParseResult } from './types.js';
|
|
3
3
|
import { type YamlMap } from './yaml.js';
|
|
4
|
+
export declare function parseFrontmatter(content: string, file?: string): FrontmatterResult;
|
|
4
5
|
export declare function parse(content: string, file?: string): ParseResult;
|
|
5
6
|
export declare function relativize(document: Document, file: string, handle: string, definition?: Definition): ApplyResult;
|
|
6
7
|
export declare function relativizeBody(body: string, file: string, handle: string): BodyResult;
|
|
7
8
|
export declare function relinkBody(body: string, file: string, moves: ReadonlyMap<string, string>): BodyResult;
|
|
8
9
|
export declare function links(document: Document, file: string): readonly Link[];
|
|
10
|
+
export declare function linkOccurrences(document: Document, file: string): readonly LinkOccurrence[];
|
|
9
11
|
export declare function bodyLinks(body: string, file: string): readonly Link[];
|
|
10
|
-
export declare function apply(document: Document,
|
|
12
|
+
export declare function apply(document: Document, changes: readonly Change[]): ApplyResult;
|
|
11
13
|
export declare function serialize(document: Document): string;
|
|
14
|
+
export declare function fieldStart(document: Document, pointer: string): number | undefined;
|
|
12
15
|
export declare function frontmatter(document: Document): YamlMap | null;
|
|
13
16
|
//# 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":"AAAA,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAehF,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,cAAc,EACd,WAAW,EAEZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAcL,KAAK,OAAO,EAEb,MAAM,WAAW,CAAC;AAmEnB,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,iBAAiB,CAW9E;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,WAAW,CAgB7D;AAmED,wBAAgB,UAAU,CACxB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,GACtB,WAAW,CAoBb;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAUrF;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,UAAU,CAeZ;AAuBD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAEvE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAG3F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,CAErE;AA4LD,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CA4CjF;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGlF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAE9D"}
|
package/dist/document.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ARRAY_INDEX, pointerSegments } from './definition.js';
|
|
2
2
|
import { hrefOccurrences } from './links.js';
|
|
3
|
-
import { relative, retarget, SHELL_ROOMS, shellTarget, target } from './paths.js';
|
|
3
|
+
import { destinationPath, relative, retarget, roomPath, roomTarget, SHELL_ROOMS, shellTarget, target, } from './paths.js';
|
|
4
4
|
import { references } from './references.js';
|
|
5
5
|
import { headingSpans, sectionsRecord } from './sections.js';
|
|
6
6
|
import { refusal } from './refuse.js';
|
|
7
|
-
import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
|
|
7
|
+
import { bodyStartAfterYaml, bodyStartOf, equalValue, extractFrontmatter, frontmatterValue, jsValue, lineEnding, mapEntry, parseYamlMapping, serializeJs, serializeKey, YamlError, } from './yaml.js';
|
|
8
8
|
const internals = new WeakMap();
|
|
9
9
|
function refuse(file, rule, location) {
|
|
10
10
|
return location === undefined ? refusal(file, rule) : refusal(file, rule, { location });
|
|
@@ -28,19 +28,10 @@ function declaredType(fields) {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function opened(content, file) {
|
|
32
32
|
const fence = extractFrontmatter(content);
|
|
33
|
-
if (fence.kind === 'none')
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
ok: true,
|
|
37
|
-
document: freezeDocument(content, undefined, {}, content.slice(0, spans[0]?.headingStart), sectionsRecord(spans, content), {
|
|
38
|
-
root: null,
|
|
39
|
-
yamlEnd: 0,
|
|
40
|
-
bodyStart: 0,
|
|
41
|
-
}),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
33
|
+
if (fence.kind === 'none')
|
|
34
|
+
return { ok: true, root: null, fields: {}, yamlEnd: 0 };
|
|
44
35
|
if (fence.kind === 'unclosed')
|
|
45
36
|
return { ok: false, refusal: refuse(file, 'frontmatter-fence') };
|
|
46
37
|
try {
|
|
@@ -49,17 +40,7 @@ export function parse(content, file = '') {
|
|
|
49
40
|
if (fields === null || typeof fields !== 'object' || Array.isArray(fields)) {
|
|
50
41
|
return { ok: false, refusal: refuse(file, 'frontmatter-fence') };
|
|
51
42
|
}
|
|
52
|
-
|
|
53
|
-
const spans = headingSpans(content, bodyStart);
|
|
54
|
-
const record = fields;
|
|
55
|
-
return {
|
|
56
|
-
ok: true,
|
|
57
|
-
document: freezeDocument(content, declaredType(record), record, content.slice(bodyStart, spans[0]?.headingStart), sectionsRecord(spans, content), {
|
|
58
|
-
root,
|
|
59
|
-
yamlEnd: fence.yamlEnd,
|
|
60
|
-
bodyStart,
|
|
61
|
-
}),
|
|
62
|
-
};
|
|
43
|
+
return { ok: true, root, fields: fields, yamlEnd: fence.yamlEnd };
|
|
63
44
|
}
|
|
64
45
|
catch (error) {
|
|
65
46
|
if (error instanceof YamlError)
|
|
@@ -67,63 +48,29 @@ export function parse(content, file = '') {
|
|
|
67
48
|
throw error;
|
|
68
49
|
}
|
|
69
50
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const existing = mapEntry(internal.root, name);
|
|
83
|
-
if (existing !== undefined) {
|
|
84
|
-
nextContent = splice(document.content, existing.value.start, existing.value.end, serializeJs(value));
|
|
85
|
-
}
|
|
86
|
-
else if (document.content[internal.root.start] === '{') {
|
|
87
|
-
nextContent = splice(document.content, internal.root.end - 1, internal.root.end - 1, `${internal.root.entries.length === 0 ? '' : ', '}${serializeKey(name)}: ${serializeJs(value)}`);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
const nl = lineEnding(document.content);
|
|
91
|
-
const lineStart = document.content.lastIndexOf('\n', internal.root.start - 1) + 1;
|
|
92
|
-
const indent = document.content.slice(lineStart, internal.root.start);
|
|
93
|
-
const line = `${indent}${serializeKey(name)}: ${serializeJs(value)}${nl}`;
|
|
94
|
-
const before = document.content.slice(0, internal.yamlEnd);
|
|
95
|
-
const prefix = before.endsWith('\n') || before.endsWith('\r\n') ? '' : nl;
|
|
96
|
-
nextContent = splice(document.content, internal.yamlEnd, internal.yamlEnd, `${prefix}${line}`);
|
|
97
|
-
}
|
|
98
|
-
const parsed = parse(nextContent);
|
|
99
|
-
if (!parsed.ok)
|
|
100
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
101
|
-
return { outcome: 'applied', document: parsed.document };
|
|
102
|
-
}
|
|
103
|
-
function applySection(document, name, value, prior) {
|
|
104
|
-
const internal = internals.get(document);
|
|
105
|
-
if (internal === undefined)
|
|
106
|
-
return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
|
|
107
|
-
const spans = headingSpans(document.content, internal.bodyStart);
|
|
108
|
-
const span = spans.find((item) => item.name === name);
|
|
109
|
-
if (span === undefined)
|
|
110
|
-
return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
|
|
111
|
-
const current = document.content.slice(span.bodyStart, span.bodyEnd);
|
|
112
|
-
if (current !== prior)
|
|
113
|
-
return { outcome: 'stale', document };
|
|
114
|
-
const parsed = parse(splice(document.content, span.bodyStart, span.bodyEnd, value));
|
|
115
|
-
if (!parsed.ok)
|
|
116
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
117
|
-
return { outcome: 'applied', document: parsed.document };
|
|
51
|
+
export function parseFrontmatter(content, file = '') {
|
|
52
|
+
const read = opened(content, file);
|
|
53
|
+
if (!read.ok)
|
|
54
|
+
return read;
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
frontmatter: Object.freeze({
|
|
58
|
+
content,
|
|
59
|
+
type: declaredType(read.fields),
|
|
60
|
+
fields: Object.freeze(read.fields),
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
118
63
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
64
|
+
export function parse(content, file = '') {
|
|
65
|
+
const read = opened(content, file);
|
|
66
|
+
if (!read.ok)
|
|
67
|
+
return read;
|
|
68
|
+
const bodyStart = read.root === null ? 0 : bodyStartAfterYaml(content, read.yamlEnd);
|
|
69
|
+
const spans = headingSpans(content, bodyStart);
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
document: freezeDocument(content, declaredType(read.fields), read.fields, content.slice(bodyStart, spans[0]?.headingStart), sectionsRecord(spans, content), { root: read.root, yamlEnd: read.yamlEnd, bodyStart }),
|
|
73
|
+
};
|
|
127
74
|
}
|
|
128
75
|
function fitsBare(to) {
|
|
129
76
|
if (to === '' || to.startsWith('<'))
|
|
@@ -152,58 +99,34 @@ function destinationText(to, bracketed) {
|
|
|
152
99
|
return `<${to}>`;
|
|
153
100
|
return null;
|
|
154
101
|
}
|
|
155
|
-
function
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
for (const match of hrefOccurrences(content.slice(bodyStart))) {
|
|
160
|
-
if (match.href !== href)
|
|
161
|
-
continue;
|
|
162
|
-
const text = destinationText(written, match.bracketed);
|
|
163
|
-
if (text === null)
|
|
164
|
-
return refuse('', 'unwritable-link', written);
|
|
165
|
-
rewritten += content.slice(cursor, bodyStart + match.start);
|
|
166
|
-
rewritten += text;
|
|
167
|
-
cursor = bodyStart + match.end;
|
|
168
|
-
}
|
|
169
|
-
return rewritten + content.slice(cursor);
|
|
170
|
-
}
|
|
171
|
-
function shellRelinked(content, bodyStart, file, handle) {
|
|
172
|
-
const hrefs = new Set(hrefOccurrences(content.slice(bodyStart)).map(({ href }) => href));
|
|
173
|
-
let held = content;
|
|
174
|
-
for (const href of hrefs) {
|
|
102
|
+
function shellRelinked(body, file, handle) {
|
|
103
|
+
const occurrences = hrefOccurrences(body);
|
|
104
|
+
const written = new Map();
|
|
105
|
+
for (const href of new Set(occurrences.map((occurrence) => occurrence.href))) {
|
|
175
106
|
const path = shellTarget(href, handle);
|
|
176
107
|
if (path === null)
|
|
177
108
|
continue;
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
return
|
|
181
|
-
|
|
109
|
+
const to = retarget(href, relative(file, path));
|
|
110
|
+
if (destinationText(to, false) === null)
|
|
111
|
+
return refuse('', 'unwritable-link', to);
|
|
112
|
+
written.set(href, to);
|
|
182
113
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return { outcome: 'applied', document: parsed.document };
|
|
194
|
-
}
|
|
195
|
-
function applyLink(document, href, to) {
|
|
196
|
-
const content = document.content;
|
|
197
|
-
const bodyStart = bodyStartOf(content);
|
|
198
|
-
if (!hrefOccurrences(content.slice(bodyStart)).some((occurrence) => occurrence.href === href))
|
|
199
|
-
return { outcome: 'stale', document };
|
|
200
|
-
return reparsed(document, relinked(content, bodyStart, href, to));
|
|
114
|
+
let rewritten = '';
|
|
115
|
+
let cursor = 0;
|
|
116
|
+
for (const match of occurrences) {
|
|
117
|
+
const to = written.get(match.href);
|
|
118
|
+
if (to === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
rewritten += body.slice(cursor, match.start) + destinationText(to, match.bracketed);
|
|
121
|
+
cursor = match.end;
|
|
122
|
+
}
|
|
123
|
+
return rewritten + body.slice(cursor);
|
|
201
124
|
}
|
|
202
|
-
function nodeAt(root, pointer) {
|
|
125
|
+
function nodeAt(root, pointer, entryOf = mapEntry) {
|
|
203
126
|
let node = root;
|
|
204
127
|
for (const key of pointerSegments(pointer)) {
|
|
205
128
|
if (node?.kind === 'map')
|
|
206
|
-
node =
|
|
129
|
+
node = entryOf(node, key)?.value;
|
|
207
130
|
else if (node?.kind === 'seq' && ARRAY_INDEX.test(key))
|
|
208
131
|
node = node.items[Number(key)];
|
|
209
132
|
else
|
|
@@ -218,37 +141,33 @@ function scalarText(source, value) {
|
|
|
218
141
|
return JSON.stringify(value);
|
|
219
142
|
return serializeJs(value);
|
|
220
143
|
}
|
|
221
|
-
function applyReference(document, pointer, value, prior) {
|
|
222
|
-
const root = internals.get(document)?.root;
|
|
223
|
-
const node = root === null || root === undefined ? undefined : nodeAt(root, pointer);
|
|
224
|
-
if (node?.kind !== 'scalar' || node.value !== prior)
|
|
225
|
-
return { outcome: 'stale', document };
|
|
226
|
-
const text = scalarText(document.content[node.start], value);
|
|
227
|
-
const parsed = parse(splice(document.content, node.start, node.end, text));
|
|
228
|
-
if (!parsed.ok)
|
|
229
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
230
|
-
return { outcome: 'applied', document: parsed.document };
|
|
231
|
-
}
|
|
232
144
|
export function relativize(document, file, handle, definition) {
|
|
233
|
-
|
|
145
|
+
const changes = [];
|
|
234
146
|
for (const { pointer, value } of definition === undefined
|
|
235
147
|
? []
|
|
236
148
|
: references(document, definition)) {
|
|
237
149
|
const path = shellTarget(value, handle);
|
|
238
|
-
if (path
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
150
|
+
if (path !== null)
|
|
151
|
+
changes.push({
|
|
152
|
+
kind: 'reference',
|
|
153
|
+
pointer,
|
|
154
|
+
value: retarget(value, relative(file, path)),
|
|
155
|
+
prior: value,
|
|
156
|
+
});
|
|
244
157
|
}
|
|
245
|
-
|
|
158
|
+
const body = document.content.slice(bodyStartOf(document.content));
|
|
159
|
+
for (const href of new Set(hrefOccurrences(body).map((occurrence) => occurrence.href))) {
|
|
160
|
+
const path = shellTarget(href, handle);
|
|
161
|
+
if (path !== null)
|
|
162
|
+
changes.push({ kind: 'link', href, to: relative(file, path) });
|
|
163
|
+
}
|
|
164
|
+
return apply(document, changes);
|
|
246
165
|
}
|
|
247
166
|
export function relativizeBody(body, file, handle) {
|
|
248
|
-
const relinkedBody = shellRelinked(body,
|
|
167
|
+
const relinkedBody = shellRelinked(body, file, handle);
|
|
249
168
|
if (typeof relinkedBody !== 'string')
|
|
250
169
|
return { outcome: 'refuse', refusal: relinkedBody };
|
|
251
|
-
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => href
|
|
170
|
+
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true && roomTarget(href) === null);
|
|
252
171
|
if (elsewhere !== undefined)
|
|
253
172
|
return { outcome: 'refuse', refusal: refuse(file, 'unresolved-link', elsewhere.href) };
|
|
254
173
|
return { outcome: 'applied', body: relinkedBody };
|
|
@@ -257,11 +176,11 @@ export function relinkBody(body, file, moves) {
|
|
|
257
176
|
let rewritten = '';
|
|
258
177
|
let cursor = 0;
|
|
259
178
|
for (const match of hrefOccurrences(body)) {
|
|
260
|
-
const from = target(file, match.href);
|
|
179
|
+
const from = target(file, match.href) ?? roomPath(match.href);
|
|
261
180
|
const to = from === null ? undefined : moves.get(from);
|
|
262
181
|
if (to === undefined)
|
|
263
182
|
continue;
|
|
264
|
-
const written = retarget(match.href, relative(file, to));
|
|
183
|
+
const written = retarget(match.href, from.startsWith(SHELL_ROOMS) ? to : relative(file, to));
|
|
265
184
|
const text = destinationText(written, match.bracketed);
|
|
266
185
|
if (text === null)
|
|
267
186
|
return { outcome: 'refuse', refusal: refuse(file, 'unwritable-link', written) };
|
|
@@ -270,48 +189,279 @@ export function relinkBody(body, file, moves) {
|
|
|
270
189
|
}
|
|
271
190
|
return { outcome: 'applied', body: rewritten + body.slice(cursor) };
|
|
272
191
|
}
|
|
192
|
+
function occurrencesIn(text, file, offset) {
|
|
193
|
+
const found = [];
|
|
194
|
+
for (const occurrence of hrefOccurrences(text)) {
|
|
195
|
+
const resolved = target(file, occurrence.href);
|
|
196
|
+
if (resolved !== null)
|
|
197
|
+
found.push({ href: occurrence.href, target: resolved, start: offset + occurrence.start });
|
|
198
|
+
}
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
273
201
|
function linksIn(text, file) {
|
|
274
202
|
const found = [];
|
|
275
203
|
const seen = new Set();
|
|
276
|
-
for (const occurrence of
|
|
204
|
+
for (const occurrence of occurrencesIn(text, file, 0)) {
|
|
277
205
|
if (seen.has(occurrence.href))
|
|
278
206
|
continue;
|
|
279
|
-
const resolved = target(file, occurrence.href);
|
|
280
|
-
if (resolved === null)
|
|
281
|
-
continue;
|
|
282
207
|
seen.add(occurrence.href);
|
|
283
|
-
found.push({ href: occurrence.href, target:
|
|
208
|
+
found.push({ href: occurrence.href, target: occurrence.target });
|
|
284
209
|
}
|
|
285
210
|
return found;
|
|
286
211
|
}
|
|
287
212
|
export function links(document, file) {
|
|
288
213
|
return linksIn(document.content.slice(bodyStartOf(document.content)), file);
|
|
289
214
|
}
|
|
215
|
+
export function linkOccurrences(document, file) {
|
|
216
|
+
const bodyStart = bodyStartOf(document.content);
|
|
217
|
+
return occurrencesIn(document.content.slice(bodyStart), file, bodyStart);
|
|
218
|
+
}
|
|
290
219
|
export function bodyLinks(body, file) {
|
|
291
220
|
return linksIn(body, file);
|
|
292
221
|
}
|
|
293
|
-
|
|
222
|
+
function internalOf(document) {
|
|
223
|
+
const held = internals.get(document);
|
|
224
|
+
if (held !== undefined)
|
|
225
|
+
return held;
|
|
226
|
+
const read = opened(document.content, '');
|
|
227
|
+
if (!read.ok)
|
|
228
|
+
return read.refusal;
|
|
229
|
+
return {
|
|
230
|
+
root: read.root,
|
|
231
|
+
yamlEnd: read.yamlEnd,
|
|
232
|
+
bodyStart: read.root === null ? 0 : bodyStartAfterYaml(document.content, read.yamlEnd),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function partOf(change) {
|
|
236
|
+
if (change.kind === 'field' || change.kind === 'section')
|
|
237
|
+
return `${change.kind} ${change.name}`;
|
|
294
238
|
if (change.kind === 'link')
|
|
295
|
-
return
|
|
239
|
+
return `link ${change.href}`;
|
|
296
240
|
if (change.kind === 'reference')
|
|
297
|
-
return
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
241
|
+
return `reference ${change.pointer}`;
|
|
242
|
+
return 'preamble';
|
|
243
|
+
}
|
|
244
|
+
function planner(document, internal) {
|
|
245
|
+
const content = document.content;
|
|
246
|
+
const keyed = new Map();
|
|
247
|
+
let spans;
|
|
248
|
+
let occurrences;
|
|
249
|
+
let ending;
|
|
250
|
+
let added = 0;
|
|
251
|
+
const entryOf = (map, key) => {
|
|
252
|
+
let index = keyed.get(map);
|
|
253
|
+
if (index === undefined) {
|
|
254
|
+
index = new Map();
|
|
255
|
+
for (const entry of map.entries)
|
|
256
|
+
if (!index.has(entry.key))
|
|
257
|
+
index.set(entry.key, entry);
|
|
258
|
+
keyed.set(map, index);
|
|
259
|
+
}
|
|
260
|
+
return index.get(key);
|
|
261
|
+
};
|
|
262
|
+
const field = (name, value, prior, at) => {
|
|
263
|
+
const root = internal.root;
|
|
264
|
+
if (root === null)
|
|
265
|
+
return refuse('', 'frontmatter-fence', name);
|
|
266
|
+
if (!equalValue(document.fields[name], prior))
|
|
267
|
+
return 'stale';
|
|
268
|
+
if (!frontmatterValue(value))
|
|
269
|
+
return refuse('', 'frontmatter-fence', name);
|
|
270
|
+
const existing = entryOf(root, name);
|
|
271
|
+
if (existing !== undefined) {
|
|
272
|
+
const held = existing.value;
|
|
273
|
+
const inline = held.start > existing.after && !/[\r\n]/.test(content.slice(existing.after, held.start));
|
|
274
|
+
return inline
|
|
275
|
+
? [{ start: held.start, end: held.end, text: serializeJs(value), at }]
|
|
276
|
+
: [{ start: existing.after, end: held.end, text: ` ${serializeJs(value)}`, at }];
|
|
277
|
+
}
|
|
278
|
+
const entry = `${serializeKey(name)}: ${serializeJs(value)}`;
|
|
279
|
+
added += 1;
|
|
280
|
+
if (content[root.start] === '{') {
|
|
281
|
+
const separator = root.entries.length === 0 && added === 1 ? '' : ', ';
|
|
282
|
+
return [{ start: root.end - 1, end: root.end - 1, text: `${separator}${entry}`, at }];
|
|
283
|
+
}
|
|
284
|
+
const lineStart = content.lastIndexOf('\n', root.start - 1) + 1;
|
|
285
|
+
ending ??= lineEnding(content);
|
|
286
|
+
const line = `${content.slice(lineStart, root.start)}${entry}${ending}`;
|
|
287
|
+
return [{ start: internal.yamlEnd, end: internal.yamlEnd, text: line, at, entry: true }];
|
|
288
|
+
};
|
|
289
|
+
const section = (name, value, prior, at) => {
|
|
290
|
+
if (spans === undefined) {
|
|
291
|
+
spans = new Map();
|
|
292
|
+
for (const span of headingSpans(content, internal.bodyStart))
|
|
293
|
+
if (!spans.has(span.name))
|
|
294
|
+
spans.set(span.name, span);
|
|
295
|
+
}
|
|
296
|
+
const span = spans.get(name);
|
|
297
|
+
if (span === undefined)
|
|
298
|
+
return refuse('', 'required-section', name);
|
|
299
|
+
if (content.slice(span.bodyStart, span.bodyEnd) !== prior)
|
|
300
|
+
return 'stale';
|
|
301
|
+
return [{ start: span.bodyStart, end: span.bodyEnd, text: value, at }];
|
|
302
|
+
};
|
|
303
|
+
const link = (href, to, at) => {
|
|
304
|
+
if (occurrences === undefined) {
|
|
305
|
+
occurrences = new Map();
|
|
306
|
+
for (const occurrence of hrefOccurrences(content.slice(internal.bodyStart))) {
|
|
307
|
+
const same = occurrences.get(occurrence.href);
|
|
308
|
+
if (same === undefined)
|
|
309
|
+
occurrences.set(occurrence.href, [occurrence]);
|
|
310
|
+
else
|
|
311
|
+
same.push(occurrence);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const found = occurrences.get(href);
|
|
315
|
+
if (found === undefined)
|
|
316
|
+
return 'stale';
|
|
317
|
+
const written = retarget(href, to);
|
|
318
|
+
if (destinationText(written, false) === null)
|
|
319
|
+
return refuse('', 'unwritable-link', written);
|
|
320
|
+
return found.map((occurrence) => ({
|
|
321
|
+
start: internal.bodyStart + occurrence.start,
|
|
322
|
+
end: internal.bodyStart + occurrence.end,
|
|
323
|
+
text: destinationText(written, occurrence.bracketed),
|
|
324
|
+
at,
|
|
325
|
+
}));
|
|
326
|
+
};
|
|
327
|
+
const reference = (pointer, value, prior, at) => {
|
|
328
|
+
const node = internal.root === null ? undefined : nodeAt(internal.root, pointer, entryOf);
|
|
329
|
+
if (node?.kind !== 'scalar' || node.value !== prior)
|
|
330
|
+
return 'stale';
|
|
331
|
+
return [
|
|
332
|
+
{ start: node.start, end: node.end, text: scalarText(content[node.start], value), at },
|
|
333
|
+
];
|
|
334
|
+
};
|
|
335
|
+
return (change, at) => {
|
|
301
336
|
if (change.kind === 'field')
|
|
302
|
-
return
|
|
303
|
-
|
|
337
|
+
return field(change.name, change.value, change.prior, at);
|
|
338
|
+
if (change.kind === 'section')
|
|
339
|
+
return section(change.name, change.value, change.prior, at);
|
|
340
|
+
if (change.kind === 'link')
|
|
341
|
+
return link(change.href, change.to, at);
|
|
342
|
+
if (change.kind === 'reference')
|
|
343
|
+
return reference(change.pointer, change.value, change.prior, at);
|
|
344
|
+
if (document.preamble !== change.prior)
|
|
345
|
+
return 'stale';
|
|
346
|
+
const start = internal.bodyStart;
|
|
347
|
+
return [{ start, end: start + document.preamble.length, text: change.value, at }];
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function overlapping(edits, stale) {
|
|
351
|
+
let reach = -1;
|
|
352
|
+
let owner = -1;
|
|
353
|
+
for (const edit of edits) {
|
|
354
|
+
if (edit.start < reach)
|
|
355
|
+
stale.add(Math.max(edit.at, owner));
|
|
356
|
+
if (edit.end > reach) {
|
|
357
|
+
reach = edit.end;
|
|
358
|
+
owner = edit.at;
|
|
359
|
+
}
|
|
304
360
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
361
|
+
}
|
|
362
|
+
function firstHeadings(content, bodyStart) {
|
|
363
|
+
const first = new Map();
|
|
364
|
+
for (const span of headingSpans(content, bodyStart)) {
|
|
365
|
+
if (!first.has(span.name))
|
|
366
|
+
first.set(span.name, span.headingStart);
|
|
367
|
+
}
|
|
368
|
+
return first;
|
|
369
|
+
}
|
|
370
|
+
function hidden(before, bodyStart, after, shifts) {
|
|
371
|
+
const placed = firstHeadings(after, bodyStartOf(after));
|
|
372
|
+
let shift = 0;
|
|
373
|
+
let index = 0;
|
|
374
|
+
for (const [name, at] of firstHeadings(before, bodyStart)) {
|
|
375
|
+
while (index < shifts.length && shifts[index][0] <= at) {
|
|
376
|
+
shift += shifts[index][1];
|
|
377
|
+
index += 1;
|
|
308
378
|
}
|
|
309
|
-
|
|
379
|
+
if (placed.get(name) !== at + shift)
|
|
380
|
+
return name;
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
function spliced(source, edits) {
|
|
385
|
+
const shifts = [];
|
|
386
|
+
const pieces = [];
|
|
387
|
+
let cursor = 0;
|
|
388
|
+
let entered = false;
|
|
389
|
+
let ended = false;
|
|
390
|
+
for (const edit of edits) {
|
|
391
|
+
const kept = source.slice(cursor, edit.start);
|
|
392
|
+
pieces.push(kept);
|
|
393
|
+
if (kept !== '')
|
|
394
|
+
ended = kept.endsWith('\n');
|
|
395
|
+
let text = edit.text;
|
|
396
|
+
if (edit.entry === true && !entered) {
|
|
397
|
+
entered = true;
|
|
398
|
+
if (!ended)
|
|
399
|
+
text = `${lineEnding(source)}${text}`;
|
|
400
|
+
}
|
|
401
|
+
pieces.push(text);
|
|
402
|
+
if (text !== '')
|
|
403
|
+
ended = text.endsWith('\n');
|
|
404
|
+
shifts.push([edit.start, text.length - (edit.end - edit.start)]);
|
|
405
|
+
cursor = edit.end;
|
|
406
|
+
}
|
|
407
|
+
pieces.push(source.slice(cursor));
|
|
408
|
+
return { content: pieces.join(''), shifts };
|
|
409
|
+
}
|
|
410
|
+
export function apply(document, changes) {
|
|
411
|
+
const internal = internalOf(document);
|
|
412
|
+
if (!('bodyStart' in internal))
|
|
413
|
+
return { outcome: 'refuse', refusal: internal };
|
|
414
|
+
const plan = planner(document, internal);
|
|
415
|
+
const edits = [];
|
|
416
|
+
const stale = new Set();
|
|
417
|
+
const named = new Set();
|
|
418
|
+
for (const [at, change] of changes.entries()) {
|
|
419
|
+
const planned = plan(change, at);
|
|
420
|
+
if (planned === 'stale') {
|
|
421
|
+
stale.add(at);
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (!Array.isArray(planned))
|
|
425
|
+
return { outcome: 'refuse', refusal: planned };
|
|
426
|
+
const part = partOf(change);
|
|
427
|
+
if (named.has(part)) {
|
|
428
|
+
stale.add(at);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
named.add(part);
|
|
432
|
+
for (const edit of planned)
|
|
433
|
+
edits.push(edit);
|
|
434
|
+
}
|
|
435
|
+
edits.sort((left, right) => left.start - right.start || left.end - right.end || left.at - right.at);
|
|
436
|
+
overlapping(edits, stale);
|
|
437
|
+
if (stale.size > 0)
|
|
438
|
+
return { outcome: 'stale', document, stale: [...stale].sort((left, right) => left - right) };
|
|
439
|
+
const source = document.content;
|
|
440
|
+
const { content } = spliced(source, edits);
|
|
441
|
+
if (content === source)
|
|
442
|
+
return { outcome: 'applied', document };
|
|
443
|
+
const replaced = edits.filter((edit) => {
|
|
444
|
+
const kind = changes[edit.at].kind;
|
|
445
|
+
return kind === 'section' || kind === 'preamble';
|
|
446
|
+
});
|
|
447
|
+
if (replaced.length > 0) {
|
|
448
|
+
const checked = spliced(source, replaced);
|
|
449
|
+
const name = hidden(source, internal.bodyStart, checked.content, checked.shifts);
|
|
450
|
+
if (name !== undefined)
|
|
451
|
+
return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
|
|
310
452
|
}
|
|
453
|
+
const parsed = parse(content);
|
|
454
|
+
if (!parsed.ok)
|
|
455
|
+
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
456
|
+
return { outcome: 'applied', document: parsed.document };
|
|
311
457
|
}
|
|
312
458
|
export function serialize(document) {
|
|
313
459
|
return document.content;
|
|
314
460
|
}
|
|
461
|
+
export function fieldStart(document, pointer) {
|
|
462
|
+
const root = internals.get(document)?.root;
|
|
463
|
+
return root === null || root === undefined ? undefined : nodeAt(root, pointer)?.start;
|
|
464
|
+
}
|
|
315
465
|
export function frontmatter(document) {
|
|
316
466
|
return internals.get(document)?.root ?? null;
|
|
317
467
|
}
|
package/dist/fields.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export declare const TYPE_FIELD = "type";
|
|
1
2
|
export declare const MEMBER_FIELD = "member";
|
|
2
3
|
export declare const SITE_FIELD = "site";
|
|
3
4
|
export declare const RECEIVED_FIELD = "received";
|
|
5
|
+
export declare const HOUSE_FIELDS: readonly string[];
|
|
4
6
|
//# sourceMappingURL=fields.d.ts.map
|
package/dist/fields.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC,eAAO,MAAM,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC,eAAO,MAAM,UAAU,SAAS,CAAC;AAEjC,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAKzC,CAAC"}
|
package/dist/fields.js
CHANGED