@agentshouse/mdmodel 0.1.0-c64451b2127b4 → 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 +170 -170
- package/README.md +15 -18
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +6 -13
- package/dist/condition.d.ts +1 -1
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +19 -9
- package/dist/definition.d.ts +15 -0
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +224 -24
- 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 +318 -182
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/links.d.ts +1 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +22 -11
- package/dist/paths.d.ts +9 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +24 -1
- 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/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/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 +1 -1
package/dist/document.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ARRAY_INDEX, pointerSegments } from './definition.js';
|
|
2
2
|
import { hrefOccurrences } from './links.js';
|
|
3
|
-
import { destinationPath, 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';
|
|
@@ -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,85 +48,29 @@ export function parse(content, file = '') {
|
|
|
67
48
|
throw error;
|
|
68
49
|
}
|
|
69
50
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
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
|
+
};
|
|
72
63
|
}
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
let nextContent;
|
|
85
|
-
const existing = mapEntry(internal.root, name);
|
|
86
|
-
if (existing !== undefined) {
|
|
87
|
-
const held = existing.value;
|
|
88
|
-
const inline = held.start > existing.after &&
|
|
89
|
-
!/[\r\n]/.test(document.content.slice(existing.after, held.start));
|
|
90
|
-
nextContent = inline
|
|
91
|
-
? splice(document.content, held.start, held.end, serializeJs(value))
|
|
92
|
-
: splice(document.content, existing.after, held.end, ` ${serializeJs(value)}`);
|
|
93
|
-
}
|
|
94
|
-
else if (document.content[internal.root.start] === '{') {
|
|
95
|
-
nextContent = splice(document.content, internal.root.end - 1, internal.root.end - 1, `${internal.root.entries.length === 0 ? '' : ', '}${serializeKey(name)}: ${serializeJs(value)}`);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
const nl = lineEnding(document.content);
|
|
99
|
-
const lineStart = document.content.lastIndexOf('\n', internal.root.start - 1) + 1;
|
|
100
|
-
const indent = document.content.slice(lineStart, internal.root.start);
|
|
101
|
-
const line = `${indent}${serializeKey(name)}: ${serializeJs(value)}${nl}`;
|
|
102
|
-
const before = document.content.slice(0, internal.yamlEnd);
|
|
103
|
-
const prefix = before.endsWith('\n') || before.endsWith('\r\n') ? '' : nl;
|
|
104
|
-
nextContent = splice(document.content, internal.yamlEnd, internal.yamlEnd, `${prefix}${line}`);
|
|
105
|
-
}
|
|
106
|
-
const parsed = parse(nextContent);
|
|
107
|
-
if (!parsed.ok)
|
|
108
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
109
|
-
return { outcome: 'applied', document: parsed.document };
|
|
110
|
-
}
|
|
111
|
-
function applySection(document, name, value, prior) {
|
|
112
|
-
const internal = internals.get(document);
|
|
113
|
-
if (internal === undefined)
|
|
114
|
-
return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
|
|
115
|
-
const spans = headingSpans(document.content, internal.bodyStart);
|
|
116
|
-
const span = spans.find((item) => item.name === name);
|
|
117
|
-
if (span === undefined)
|
|
118
|
-
return { outcome: 'refuse', refusal: refuse('', 'required-section', name) };
|
|
119
|
-
const current = document.content.slice(span.bodyStart, span.bodyEnd);
|
|
120
|
-
if (current !== prior)
|
|
121
|
-
return { outcome: 'stale', document };
|
|
122
|
-
return replaced(document, internal.bodyStart, span.bodyStart, span.bodyEnd, value);
|
|
123
|
-
}
|
|
124
|
-
function applyPreamble(document, value, prior) {
|
|
125
|
-
if (document.preamble !== prior)
|
|
126
|
-
return { outcome: 'stale', document };
|
|
127
|
-
const start = bodyStartOf(document.content);
|
|
128
|
-
return replaced(document, start, start, start + document.preamble.length, value);
|
|
129
|
-
}
|
|
130
|
-
function firstHeadings(content, bodyStart) {
|
|
131
|
-
const first = new Map();
|
|
132
|
-
for (const span of headingSpans(content, bodyStart)) {
|
|
133
|
-
if (!first.has(span.name))
|
|
134
|
-
first.set(span.name, span.headingStart);
|
|
135
|
-
}
|
|
136
|
-
return first;
|
|
137
|
-
}
|
|
138
|
-
function replaced(document, bodyStart, start, end, value) {
|
|
139
|
-
const content = splice(document.content, start, end, value);
|
|
140
|
-
const after = firstHeadings(content, bodyStartOf(content));
|
|
141
|
-
for (const [name, at] of firstHeadings(document.content, bodyStart)) {
|
|
142
|
-
if (after.get(name) !== (at < start ? at : at + value.length - (end - start)))
|
|
143
|
-
return { outcome: 'refuse', refusal: refuse('', 'overlapping-section', name) };
|
|
144
|
-
}
|
|
145
|
-
const parsed = parse(content);
|
|
146
|
-
if (!parsed.ok)
|
|
147
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
148
|
-
return { outcome: 'applied', document: parsed.document };
|
|
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
|
+
};
|
|
149
74
|
}
|
|
150
75
|
function fitsBare(to) {
|
|
151
76
|
if (to === '' || to.startsWith('<'))
|
|
@@ -174,58 +99,34 @@ function destinationText(to, bracketed) {
|
|
|
174
99
|
return `<${to}>`;
|
|
175
100
|
return null;
|
|
176
101
|
}
|
|
177
|
-
function
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const match of hrefOccurrences(content.slice(bodyStart))) {
|
|
182
|
-
if (match.href !== href)
|
|
183
|
-
continue;
|
|
184
|
-
const text = destinationText(written, match.bracketed);
|
|
185
|
-
if (text === null)
|
|
186
|
-
return refuse('', 'unwritable-link', written);
|
|
187
|
-
rewritten += content.slice(cursor, bodyStart + match.start);
|
|
188
|
-
rewritten += text;
|
|
189
|
-
cursor = bodyStart + match.end;
|
|
190
|
-
}
|
|
191
|
-
return rewritten + content.slice(cursor);
|
|
192
|
-
}
|
|
193
|
-
function shellRelinked(content, bodyStart, file, handle) {
|
|
194
|
-
const hrefs = new Set(hrefOccurrences(content.slice(bodyStart)).map(({ href }) => href));
|
|
195
|
-
let held = content;
|
|
196
|
-
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))) {
|
|
197
106
|
const path = shellTarget(href, handle);
|
|
198
107
|
if (path === null)
|
|
199
108
|
continue;
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
return
|
|
203
|
-
|
|
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);
|
|
204
113
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return { outcome: 'applied', document: parsed.document };
|
|
216
|
-
}
|
|
217
|
-
function applyLink(document, href, to) {
|
|
218
|
-
const content = document.content;
|
|
219
|
-
const bodyStart = bodyStartOf(content);
|
|
220
|
-
if (!hrefOccurrences(content.slice(bodyStart)).some((occurrence) => occurrence.href === href))
|
|
221
|
-
return { outcome: 'stale', document };
|
|
222
|
-
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);
|
|
223
124
|
}
|
|
224
|
-
function nodeAt(root, pointer) {
|
|
125
|
+
function nodeAt(root, pointer, entryOf = mapEntry) {
|
|
225
126
|
let node = root;
|
|
226
127
|
for (const key of pointerSegments(pointer)) {
|
|
227
128
|
if (node?.kind === 'map')
|
|
228
|
-
node =
|
|
129
|
+
node = entryOf(node, key)?.value;
|
|
229
130
|
else if (node?.kind === 'seq' && ARRAY_INDEX.test(key))
|
|
230
131
|
node = node.items[Number(key)];
|
|
231
132
|
else
|
|
@@ -240,37 +141,33 @@ function scalarText(source, value) {
|
|
|
240
141
|
return JSON.stringify(value);
|
|
241
142
|
return serializeJs(value);
|
|
242
143
|
}
|
|
243
|
-
function applyReference(document, pointer, value, prior) {
|
|
244
|
-
const root = internals.get(document)?.root;
|
|
245
|
-
const node = root === null || root === undefined ? undefined : nodeAt(root, pointer);
|
|
246
|
-
if (node?.kind !== 'scalar' || node.value !== prior)
|
|
247
|
-
return { outcome: 'stale', document };
|
|
248
|
-
const text = scalarText(document.content[node.start], value);
|
|
249
|
-
const parsed = parse(splice(document.content, node.start, node.end, text));
|
|
250
|
-
if (!parsed.ok)
|
|
251
|
-
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
252
|
-
return { outcome: 'applied', document: parsed.document };
|
|
253
|
-
}
|
|
254
144
|
export function relativize(document, file, handle, definition) {
|
|
255
|
-
|
|
145
|
+
const changes = [];
|
|
256
146
|
for (const { pointer, value } of definition === undefined
|
|
257
147
|
? []
|
|
258
148
|
: references(document, definition)) {
|
|
259
149
|
const path = shellTarget(value, handle);
|
|
260
|
-
if (path
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
150
|
+
if (path !== null)
|
|
151
|
+
changes.push({
|
|
152
|
+
kind: 'reference',
|
|
153
|
+
pointer,
|
|
154
|
+
value: retarget(value, relative(file, path)),
|
|
155
|
+
prior: value,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
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) });
|
|
266
163
|
}
|
|
267
|
-
return
|
|
164
|
+
return apply(document, changes);
|
|
268
165
|
}
|
|
269
166
|
export function relativizeBody(body, file, handle) {
|
|
270
|
-
const relinkedBody = shellRelinked(body,
|
|
167
|
+
const relinkedBody = shellRelinked(body, file, handle);
|
|
271
168
|
if (typeof relinkedBody !== 'string')
|
|
272
169
|
return { outcome: 'refuse', refusal: relinkedBody };
|
|
273
|
-
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true);
|
|
170
|
+
const elsewhere = hrefOccurrences(relinkedBody).find(({ href }) => destinationPath(href)?.startsWith(SHELL_ROOMS) === true && roomTarget(href) === null);
|
|
274
171
|
if (elsewhere !== undefined)
|
|
275
172
|
return { outcome: 'refuse', refusal: refuse(file, 'unresolved-link', elsewhere.href) };
|
|
276
173
|
return { outcome: 'applied', body: relinkedBody };
|
|
@@ -279,11 +176,11 @@ export function relinkBody(body, file, moves) {
|
|
|
279
176
|
let rewritten = '';
|
|
280
177
|
let cursor = 0;
|
|
281
178
|
for (const match of hrefOccurrences(body)) {
|
|
282
|
-
const from = target(file, match.href);
|
|
179
|
+
const from = target(file, match.href) ?? roomPath(match.href);
|
|
283
180
|
const to = from === null ? undefined : moves.get(from);
|
|
284
181
|
if (to === undefined)
|
|
285
182
|
continue;
|
|
286
|
-
const written = retarget(match.href, relative(file, to));
|
|
183
|
+
const written = retarget(match.href, from.startsWith(SHELL_ROOMS) ? to : relative(file, to));
|
|
287
184
|
const text = destinationText(written, match.bracketed);
|
|
288
185
|
if (text === null)
|
|
289
186
|
return { outcome: 'refuse', refusal: refuse(file, 'unwritable-link', written) };
|
|
@@ -292,40 +189,279 @@ export function relinkBody(body, file, moves) {
|
|
|
292
189
|
}
|
|
293
190
|
return { outcome: 'applied', body: rewritten + body.slice(cursor) };
|
|
294
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
|
+
}
|
|
295
201
|
function linksIn(text, file) {
|
|
296
202
|
const found = [];
|
|
297
203
|
const seen = new Set();
|
|
298
|
-
for (const occurrence of
|
|
204
|
+
for (const occurrence of occurrencesIn(text, file, 0)) {
|
|
299
205
|
if (seen.has(occurrence.href))
|
|
300
206
|
continue;
|
|
301
|
-
const resolved = target(file, occurrence.href);
|
|
302
|
-
if (resolved === null)
|
|
303
|
-
continue;
|
|
304
207
|
seen.add(occurrence.href);
|
|
305
|
-
found.push({ href: occurrence.href, target:
|
|
208
|
+
found.push({ href: occurrence.href, target: occurrence.target });
|
|
306
209
|
}
|
|
307
210
|
return found;
|
|
308
211
|
}
|
|
309
212
|
export function links(document, file) {
|
|
310
213
|
return linksIn(document.content.slice(bodyStartOf(document.content)), file);
|
|
311
214
|
}
|
|
215
|
+
export function linkOccurrences(document, file) {
|
|
216
|
+
const bodyStart = bodyStartOf(document.content);
|
|
217
|
+
return occurrencesIn(document.content.slice(bodyStart), file, bodyStart);
|
|
218
|
+
}
|
|
312
219
|
export function bodyLinks(body, file) {
|
|
313
220
|
return linksIn(body, file);
|
|
314
221
|
}
|
|
315
|
-
|
|
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}`;
|
|
316
238
|
if (change.kind === 'link')
|
|
317
|
-
return
|
|
239
|
+
return `link ${change.href}`;
|
|
318
240
|
if (change.kind === 'reference')
|
|
319
|
-
return
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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) => {
|
|
336
|
+
if (change.kind === 'field')
|
|
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
|
+
}
|
|
360
|
+
}
|
|
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;
|
|
378
|
+
}
|
|
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) };
|
|
452
|
+
}
|
|
453
|
+
const parsed = parse(content);
|
|
454
|
+
if (!parsed.ok)
|
|
455
|
+
return { outcome: 'refuse', refusal: parsed.refusal };
|
|
456
|
+
return { outcome: 'applied', document: parsed.document };
|
|
325
457
|
}
|
|
326
458
|
export function serialize(document) {
|
|
327
459
|
return document.content;
|
|
328
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
|
+
}
|
|
329
465
|
export function frontmatter(document) {
|
|
330
466
|
return internals.get(document)?.root ?? null;
|
|
331
467
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
|
|
1
|
+
export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, members, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
|
|
2
2
|
export type { Actor, Condition, ConditionScope, ConditionTest, ConditionValue, CronCondition, DateCondition, DocumentCondition, DocumentEvent, DocumentFacts, Fact, FactKind, ItemCondition, ItemFacts, Resolved, WriteDoor, } from './condition.js';
|
|
3
|
+
export { headingLines } from './blocks.js';
|
|
4
|
+
export type { HeadingLine } from './blocks.js';
|
|
3
5
|
export { divide } from './divide.js';
|
|
4
|
-
export { apply, bodyLinks, links, parse, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
|
|
6
|
+
export { apply, bodyLinks, fieldStart, linkOccurrences, links, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
|
|
5
7
|
export { HOUSE_FIELDS, MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
|
|
6
|
-
export { relative, retarget, target } from './paths.js';
|
|
8
|
+
export { relative, retarget, roomPath, roomTarget, target } from './paths.js';
|
|
9
|
+
export type { RoomTarget } from './paths.js';
|
|
7
10
|
export { MARKDOWN_SYNTAX_PROFILE } from './profile.js';
|
|
8
11
|
export { project } from './project.js';
|
|
9
12
|
export { references } from './references.js';
|
|
10
13
|
export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
|
|
11
|
-
export type { Definition, JsonType, PatternFacts, PointerFacts, SectionSpec, Shape, ValueFacts, } from './definition.js';
|
|
12
|
-
export type { ApplyResult, BodyResult, Change, Division, DivisionContext, Document, Library, ModelVerification, Link, ParseResult, Projection, ProjectionContext, Reference, Refusal, Span, Validation, ValidationContext, VerifiedModel, } from './types.js';
|
|
13
|
-
export { validate, verify } from './validate.js';
|
|
14
|
+
export type { Definition, FieldRules, JsonType, PatternFacts, PointerFacts, RuleValue, SectionSpec, Shape, Transition, ValueFacts, } from './definition.js';
|
|
15
|
+
export type { ApplyResult, BodyResult, Change, Division, DivisionContext, Document, DocumentCheck, Frontmatter, FrontmatterResult, Library, ModelVerification, Link, LinkOccurrence, ParseResult, Projection, ProjectionContext, Reached, Reference, Refusal, RoomReference, Span, Validation, ValidationContext, VerifiedModel, } from './types.js';
|
|
16
|
+
export { bodyRoomLinks, checkDocument, resolves, roomReferences, sectionReferences, validate, validating, verify, } from './validate.js';
|
|
14
17
|
export { VERSION } from './version.js';
|
|
15
|
-
export { equalValue, frontmatterValue } from './yaml.js';
|
|
18
|
+
export { bodyAfterFrontmatter, equalValue, frontmatterValue } from './yaml.js';
|
|
16
19
|
export type { FrontmatterValue } from './yaml.js';
|
|
17
20
|
//# 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,EACL,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,KAAK,EACL,KAAK,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,KAAK,EACL,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,SAAS,EACT,QAAQ,EACR,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,KAAK,EACL,SAAS,EACT,KAAK,EACL,KAAK,EACL,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,KAAK,EACL,KAAK,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,OAAO,EACP,UAAU,EACV,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,KAAK,EACL,SAAS,EACT,cAAc,EACd,aAAa,EACb,cAAc,EACd,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,SAAS,EACT,QAAQ,EACR,SAAS,GACV,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,KAAK,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,KAAK,EACL,KAAK,EACL,gBAAgB,EAChB,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC9E,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,EACX,KAAK,EACL,UAAU,EACV,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,WAAW,EACX,UAAU,EACV,MAAM,EACN,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,iBAAiB,EACjB,IAAI,EACJ,cAAc,EACd,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,OAAO,EACP,SAAS,EACT,OAAO,EACP,aAAa,EACb,IAAI,EACJ,UAAU,EACV,iBAAiB,EACjB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,aAAa,EACb,QAAQ,EACR,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
|
|
1
|
+
export { dated, datePointers, factKind, facts, fires, holds, instant, itemFires, members, pointed, prosePaths, WRITE_DOORS, } from './condition.js';
|
|
2
|
+
export { headingLines } from './blocks.js';
|
|
2
3
|
export { divide } from './divide.js';
|
|
3
|
-
export { apply, bodyLinks, links, parse, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
|
|
4
|
+
export { apply, bodyLinks, fieldStart, linkOccurrences, links, parse, parseFrontmatter, relativize, relativizeBody, relinkBody, serialize, } from './document.js';
|
|
4
5
|
export { HOUSE_FIELDS, MEMBER_FIELD, RECEIVED_FIELD, SITE_FIELD, TYPE_FIELD } from './fields.js';
|
|
5
|
-
export { relative, retarget, target } from './paths.js';
|
|
6
|
+
export { relative, retarget, roomPath, roomTarget, target } from './paths.js';
|
|
6
7
|
export { MARKDOWN_SYNTAX_PROFILE } from './profile.js';
|
|
7
8
|
export { project } from './project.js';
|
|
8
9
|
export { references } from './references.js';
|
|
9
10
|
export { ARRAY_INDEX, ATTACHMENT, crossedFacts, escapePointer, itemFacts, memberFacts, pointerFacts, pointerSegments, } from './definition.js';
|
|
10
|
-
export { validate, verify } from './validate.js';
|
|
11
|
+
export { bodyRoomLinks, checkDocument, resolves, roomReferences, sectionReferences, validate, validating, verify, } from './validate.js';
|
|
11
12
|
export { VERSION } from './version.js';
|
|
12
|
-
export { equalValue, frontmatterValue } from './yaml.js';
|
|
13
|
+
export { bodyAfterFrontmatter, equalValue, frontmatterValue } from './yaml.js';
|
package/dist/links.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function escapes(text: string, index: number): boolean;
|
|
2
|
+
export declare function spanCursor(spans: readonly (readonly [number, number])[]): (index: number) => number | undefined;
|
|
2
3
|
export type HrefOccurrence = {
|
|
3
4
|
readonly href: string;
|
|
4
5
|
readonly start: number;
|