@effected/markdown 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Frontmatter.js +272 -0
- package/FrontmatterResolver.js +273 -0
- package/JsonFrontmatter.js +46 -0
- package/LICENSE +21 -0
- package/Markdown.js +251 -0
- package/MarkdownDiagnostic.js +85 -0
- package/MarkdownDocument.js +248 -0
- package/MarkdownEdit.js +52 -0
- package/MarkdownFormat.js +380 -0
- package/MarkdownNode.js +641 -0
- package/MarkdownVisitor.js +88 -0
- package/Mdast.js +384 -0
- package/README.md +255 -0
- package/TomlFrontmatter.js +44 -0
- package/YamlFrontmatter.js +45 -0
- package/index.d.ts +2190 -0
- package/index.js +15 -0
- package/internal/blockParser.js +419 -0
- package/internal/blockRegistry.js +90 -0
- package/internal/blockTypes.js +27 -0
- package/internal/blocks/atxHeading.js +54 -0
- package/internal/blocks/blockquote.js +40 -0
- package/internal/blocks/code.js +90 -0
- package/internal/blocks/document.js +17 -0
- package/internal/blocks/fencedCode.js +26 -0
- package/internal/blocks/footnoteDefinition.js +84 -0
- package/internal/blocks/frontmatter.js +56 -0
- package/internal/blocks/htmlBlock.js +72 -0
- package/internal/blocks/indentedCode.js +16 -0
- package/internal/blocks/linkReferenceDefinition.js +72 -0
- package/internal/blocks/list.js +159 -0
- package/internal/blocks/paragraph.js +27 -0
- package/internal/blocks/setextHeading.js +24 -0
- package/internal/blocks/table.js +378 -0
- package/internal/blocks/taskListItem.js +36 -0
- package/internal/blocks/thematicBreak.js +35 -0
- package/internal/carriers.js +42 -0
- package/internal/entities.js +26 -0
- package/internal/entityMap.js +7 -0
- package/internal/htmlTags.js +18 -0
- package/internal/inlineNode.js +54 -0
- package/internal/inlineParser.js +403 -0
- package/internal/inlineRegistry.js +68 -0
- package/internal/inlines/autolink.js +36 -0
- package/internal/inlines/autolinkLiteral.js +374 -0
- package/internal/inlines/codeSpan.js +32 -0
- package/internal/inlines/emphasis.js +79 -0
- package/internal/inlines/entity.js +21 -0
- package/internal/inlines/escape.js +34 -0
- package/internal/inlines/footnoteReference.js +63 -0
- package/internal/inlines/lineBreak.js +25 -0
- package/internal/inlines/link.js +212 -0
- package/internal/inlines/rawHtml.js +27 -0
- package/internal/inlines/strikethrough.js +81 -0
- package/internal/inlines/text.js +22 -0
- package/internal/lineIndex.js +76 -0
- package/internal/patterns.js +26 -0
- package/internal/preprocess.js +58 -0
- package/internal/rawInline.js +57 -0
- package/internal/references.js +160 -0
- package/internal/segments.js +36 -0
- package/internal/stringify.js +519 -0
- package/internal/unescape.js +18 -0
- package/package.json +61 -0
- package/tsdoc-metadata.json +11 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { MarkdownDiagnostic } from "./MarkdownDiagnostic.js";
|
|
2
|
+
import { Data, Stream } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/MarkdownVisitor.ts
|
|
5
|
+
/**
|
|
6
|
+
* Constructors and matchers for the `MarkdownVisitorEvent` union (e.g.
|
|
7
|
+
* `MarkdownVisitorEvent.Enter({ node, path, depth })`,
|
|
8
|
+
* `MarkdownVisitorEvent.$is("Exit")`).
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
const MarkdownVisitorEvent = Data.taggedEnum();
|
|
13
|
+
/**
|
|
14
|
+
* SAX-style markdown tree visitor statics. Not instantiable.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
var MarkdownVisitor = class {
|
|
19
|
+
constructor() {}
|
|
20
|
+
/**
|
|
21
|
+
* Create a lazy `Stream` of `MarkdownVisitorEvent` walking `root` in
|
|
22
|
+
* document pre-order: `Enter` and `Exit` for every node including the
|
|
23
|
+
* root and every leaf. Events are produced on demand, so combining with
|
|
24
|
+
* `Stream.take` allows efficient partial scans without walking the rest
|
|
25
|
+
* of the tree.
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Infallible at the type level. A tree produced by `Markdown.parse` can
|
|
29
|
+
* never trip the walk's depth guard — the parser refuses deeper input —
|
|
30
|
+
* but a decoded foreign tree (`Mdast.fromMdast`) can; the trip surfaces
|
|
31
|
+
* as a terminal `MarkdownVisitorEvent` `Error` event carrying a
|
|
32
|
+
* `NestingDepthExceeded` {@link MarkdownDiagnostic}, mirroring the typed
|
|
33
|
+
* failure `Markdown.stringify` produces for the same tree.
|
|
34
|
+
*/
|
|
35
|
+
static visit(root) {
|
|
36
|
+
return Stream.fromIterable(walkIterable(root));
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/** Lazy per-subscription iterable so each run of the stream walks afresh. */
|
|
40
|
+
function walkIterable(root) {
|
|
41
|
+
return { [Symbol.iterator]() {
|
|
42
|
+
return walk(root)[Symbol.iterator]();
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
function* walk(root) {
|
|
46
|
+
yield* walkNode(root, [], 0);
|
|
47
|
+
}
|
|
48
|
+
function* walkNode(node, path, depth) {
|
|
49
|
+
if (depth > 256) {
|
|
50
|
+
yield MarkdownVisitorEvent.Error({
|
|
51
|
+
diagnostic: MarkdownDiagnostic.make({
|
|
52
|
+
code: "NestingDepthExceeded",
|
|
53
|
+
message: `NestingDepthExceeded: limit ${256}, actual ${depth}`,
|
|
54
|
+
offset: node.position.start.offset,
|
|
55
|
+
length: Math.max(0, node.position.end.offset - node.position.start.offset),
|
|
56
|
+
line: Math.max(0, node.position.start.line - 1),
|
|
57
|
+
character: Math.max(0, node.position.start.column - 1)
|
|
58
|
+
}),
|
|
59
|
+
path,
|
|
60
|
+
depth
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
yield MarkdownVisitorEvent.Enter({
|
|
65
|
+
node,
|
|
66
|
+
path,
|
|
67
|
+
depth
|
|
68
|
+
});
|
|
69
|
+
if ("children" in node) {
|
|
70
|
+
const children = node.children;
|
|
71
|
+
for (let index = 0; index < children.length; index++) {
|
|
72
|
+
const child = children[index];
|
|
73
|
+
const events = walkNode(child, [...path, index], depth + 1);
|
|
74
|
+
for (const event of events) {
|
|
75
|
+
yield event;
|
|
76
|
+
if (event._tag === "Error") return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
yield MarkdownVisitorEvent.Exit({
|
|
81
|
+
node,
|
|
82
|
+
path,
|
|
83
|
+
depth
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { MarkdownVisitor, MarkdownVisitorEvent };
|
package/Mdast.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { Root } from "./MarkdownNode.js";
|
|
2
|
+
import { unescapeString } from "./internal/unescape.js";
|
|
3
|
+
import { Effect, Result, Schema } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/Mdast.ts
|
|
6
|
+
/**
|
|
7
|
+
* The remark-ecosystem interop boundary: projection between this package's
|
|
8
|
+
* node classes and plain mdast JSON.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* `Mdast.toMdast` projects a parsed {@link Root} to plain spec-valid mdast
|
|
12
|
+
* objects — fidelity extras stripped, optional fields spelled the way
|
|
13
|
+
* `mdast-util-from-markdown` spells them (explicit `null`/`false` where the
|
|
14
|
+
* reference utility emits them), so the output deep-equals what the remark
|
|
15
|
+
* ecosystem produces and consumes. `Mdast.fromMdast` admits foreign plain
|
|
16
|
+
* mdast back into the package's Schema node classes, synthesizing zero-width
|
|
17
|
+
* sentinel positions where unist leaves them optional.
|
|
18
|
+
*
|
|
19
|
+
* The emission target is `mdast-util-from-markdown@2.0.3` (the vendored
|
|
20
|
+
* interop corpus pin): `list.ordered`/`start`/`spread` and
|
|
21
|
+
* `listItem.spread`/`checked` always explicit (`null` for unknown `start`/
|
|
22
|
+
* `checked`), `code.lang`/`meta` and every `title` explicit `null` when
|
|
23
|
+
* absent, `image.alt`/`imageReference.alt` always a string. Under unist's
|
|
24
|
+
* null-equals-absent convention for optional fields this stays spec-valid
|
|
25
|
+
* mdast; it is also byte-identical to the reference utility's output, which
|
|
26
|
+
* is what interop means operationally. GFM shapes follow the same convention
|
|
27
|
+
* from `mdast-util-gfm`; the frontmatter capture projects to mdast's
|
|
28
|
+
* `yaml`/`toml` literal nodes, and a `json` capture projects to a
|
|
29
|
+
* `json`-typed literal node per the `mdast-util-frontmatter` custom-preset
|
|
30
|
+
* convention (presets name the node type after the language).
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Indicates that foreign mdast input failed to decode into the package's
|
|
34
|
+
* node classes.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* `issue` carries the **structured** schema failure — at runtime a
|
|
38
|
+
* `SchemaIssue.Issue` tree, reachable through `_tag` and nested `issues` —
|
|
39
|
+
* never a stringified rendering (the `FrontmatterValidationError`
|
|
40
|
+
* precedent). It is typed `unknown` because v4 exposes no `Schema` for
|
|
41
|
+
* `Issue`; narrow it with the `SchemaIssue` module.
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
var MdastDecodeError = class extends Schema.TaggedErrorClass()("MdastDecodeError", {
|
|
46
|
+
/** The structured schema issue. Never a string. */
|
|
47
|
+
issue: Schema.Defect() }) {
|
|
48
|
+
get message() {
|
|
49
|
+
return "mdast input failed to decode into markdown nodes";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** The zero-width sentinel position synthesized for foreign nodes. */
|
|
53
|
+
const sentinelPosition = {
|
|
54
|
+
start: {
|
|
55
|
+
line: 1,
|
|
56
|
+
column: 1,
|
|
57
|
+
offset: 0
|
|
58
|
+
},
|
|
59
|
+
end: {
|
|
60
|
+
line: 1,
|
|
61
|
+
column: 1,
|
|
62
|
+
offset: 0
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const projectPosition = (position) => ({
|
|
66
|
+
start: {
|
|
67
|
+
line: position.start.line,
|
|
68
|
+
column: position.start.column,
|
|
69
|
+
offset: position.start.offset
|
|
70
|
+
},
|
|
71
|
+
end: {
|
|
72
|
+
line: position.end.line,
|
|
73
|
+
column: position.end.column,
|
|
74
|
+
offset: position.end.offset
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const projectChildren = (children) => children.map((child) => projectNode(child));
|
|
78
|
+
const stripFinalLineEnding = (value) => value.endsWith("\r\n") ? value.slice(0, -2) : value.endsWith("\n") ? value.slice(0, -1) : value;
|
|
79
|
+
const projectLabel = (label) => label === void 0 ? {} : { label: unescapeString(label) };
|
|
80
|
+
const listSpread = (node) => {
|
|
81
|
+
for (let index = 0; index + 1 < node.children.length; index += 1) {
|
|
82
|
+
const current = node.children[index];
|
|
83
|
+
const next = node.children[index + 1];
|
|
84
|
+
if (current !== void 0 && next !== void 0 && next.position.start.line - current.position.end.line >= 2) return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
};
|
|
88
|
+
const projectNode = (node) => {
|
|
89
|
+
const position = projectPosition(node.position);
|
|
90
|
+
switch (node.type) {
|
|
91
|
+
case "root": return {
|
|
92
|
+
type: "root",
|
|
93
|
+
children: projectChildren(node.children),
|
|
94
|
+
position
|
|
95
|
+
};
|
|
96
|
+
case "paragraph":
|
|
97
|
+
case "blockquote":
|
|
98
|
+
case "emphasis":
|
|
99
|
+
case "strong":
|
|
100
|
+
case "delete":
|
|
101
|
+
case "tableRow":
|
|
102
|
+
case "tableCell": return {
|
|
103
|
+
type: node.type,
|
|
104
|
+
children: projectChildren(node.children),
|
|
105
|
+
position
|
|
106
|
+
};
|
|
107
|
+
case "heading": return {
|
|
108
|
+
type: "heading",
|
|
109
|
+
depth: node.depth,
|
|
110
|
+
children: projectChildren(node.children),
|
|
111
|
+
position
|
|
112
|
+
};
|
|
113
|
+
case "text":
|
|
114
|
+
case "html":
|
|
115
|
+
case "inlineCode": return {
|
|
116
|
+
type: node.type,
|
|
117
|
+
value: node.value,
|
|
118
|
+
position
|
|
119
|
+
};
|
|
120
|
+
case "break":
|
|
121
|
+
case "thematicBreak": return {
|
|
122
|
+
type: node.type,
|
|
123
|
+
position
|
|
124
|
+
};
|
|
125
|
+
case "code": return {
|
|
126
|
+
type: "code",
|
|
127
|
+
lang: node.lang ?? null,
|
|
128
|
+
meta: node.meta ?? null,
|
|
129
|
+
value: stripFinalLineEnding(node.value),
|
|
130
|
+
position
|
|
131
|
+
};
|
|
132
|
+
case "link": return {
|
|
133
|
+
type: "link",
|
|
134
|
+
title: node.title ?? null,
|
|
135
|
+
url: node.url,
|
|
136
|
+
children: projectChildren(node.children),
|
|
137
|
+
position
|
|
138
|
+
};
|
|
139
|
+
case "image": return {
|
|
140
|
+
type: "image",
|
|
141
|
+
title: node.title ?? null,
|
|
142
|
+
url: node.url,
|
|
143
|
+
alt: node.alt ?? "",
|
|
144
|
+
position
|
|
145
|
+
};
|
|
146
|
+
case "linkReference": return {
|
|
147
|
+
type: "linkReference",
|
|
148
|
+
children: projectChildren(node.children),
|
|
149
|
+
position,
|
|
150
|
+
...projectLabel(node.label),
|
|
151
|
+
identifier: node.identifier,
|
|
152
|
+
referenceType: node.referenceType
|
|
153
|
+
};
|
|
154
|
+
case "imageReference": return {
|
|
155
|
+
type: "imageReference",
|
|
156
|
+
alt: node.alt ?? "",
|
|
157
|
+
position,
|
|
158
|
+
...projectLabel(node.label),
|
|
159
|
+
identifier: node.identifier,
|
|
160
|
+
referenceType: node.referenceType
|
|
161
|
+
};
|
|
162
|
+
case "definition": return {
|
|
163
|
+
type: "definition",
|
|
164
|
+
identifier: node.identifier,
|
|
165
|
+
...projectLabel(node.label),
|
|
166
|
+
title: node.title ?? null,
|
|
167
|
+
url: node.url,
|
|
168
|
+
position
|
|
169
|
+
};
|
|
170
|
+
case "footnoteReference": return {
|
|
171
|
+
type: "footnoteReference",
|
|
172
|
+
identifier: node.identifier,
|
|
173
|
+
...projectLabel(node.label),
|
|
174
|
+
position
|
|
175
|
+
};
|
|
176
|
+
case "footnoteDefinition": return {
|
|
177
|
+
type: "footnoteDefinition",
|
|
178
|
+
identifier: node.identifier,
|
|
179
|
+
...projectLabel(node.label),
|
|
180
|
+
children: projectChildren(node.children),
|
|
181
|
+
position
|
|
182
|
+
};
|
|
183
|
+
case "list": return {
|
|
184
|
+
type: "list",
|
|
185
|
+
ordered: node.ordered ?? false,
|
|
186
|
+
start: node.start ?? null,
|
|
187
|
+
spread: listSpread(node),
|
|
188
|
+
children: projectChildren(node.children),
|
|
189
|
+
position
|
|
190
|
+
};
|
|
191
|
+
case "listItem": return {
|
|
192
|
+
type: "listItem",
|
|
193
|
+
spread: node.spread ?? false,
|
|
194
|
+
checked: node.checked ?? null,
|
|
195
|
+
children: projectChildren(node.children),
|
|
196
|
+
position
|
|
197
|
+
};
|
|
198
|
+
case "table": return {
|
|
199
|
+
type: "table",
|
|
200
|
+
align: node.align === void 0 ? null : [...node.align],
|
|
201
|
+
children: projectChildren(node.children),
|
|
202
|
+
position
|
|
203
|
+
};
|
|
204
|
+
case "frontmatter": return {
|
|
205
|
+
type: node.format,
|
|
206
|
+
value: node.value,
|
|
207
|
+
position
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/** The frontmatter literal node types foreign mdast spells per format. */
|
|
212
|
+
const frontmatterTypes = /* @__PURE__ */ new Map([
|
|
213
|
+
["yaml", "yaml"],
|
|
214
|
+
["toml", "toml"],
|
|
215
|
+
["json", "json"]
|
|
216
|
+
]);
|
|
217
|
+
/**
|
|
218
|
+
* Fields admitted per foreign node type, beyond `type`/`position`/`children`.
|
|
219
|
+
* Unknown fields (`data`, custom extensions) are dropped at the boundary;
|
|
220
|
+
* `null` values on optional fields normalize to absence per unist's
|
|
221
|
+
* null-equals-absent convention.
|
|
222
|
+
*/
|
|
223
|
+
const admittedFields = {
|
|
224
|
+
root: [],
|
|
225
|
+
paragraph: [],
|
|
226
|
+
blockquote: [],
|
|
227
|
+
emphasis: [],
|
|
228
|
+
strong: [],
|
|
229
|
+
delete: [],
|
|
230
|
+
tableRow: [],
|
|
231
|
+
tableCell: [],
|
|
232
|
+
break: [],
|
|
233
|
+
thematicBreak: [],
|
|
234
|
+
text: ["value"],
|
|
235
|
+
html: ["value"],
|
|
236
|
+
inlineCode: ["value"],
|
|
237
|
+
heading: ["depth"],
|
|
238
|
+
code: [
|
|
239
|
+
"value",
|
|
240
|
+
"lang",
|
|
241
|
+
"meta"
|
|
242
|
+
],
|
|
243
|
+
link: ["url", "title"],
|
|
244
|
+
image: [
|
|
245
|
+
"url",
|
|
246
|
+
"title",
|
|
247
|
+
"alt"
|
|
248
|
+
],
|
|
249
|
+
linkReference: [
|
|
250
|
+
"identifier",
|
|
251
|
+
"label",
|
|
252
|
+
"referenceType"
|
|
253
|
+
],
|
|
254
|
+
imageReference: [
|
|
255
|
+
"identifier",
|
|
256
|
+
"label",
|
|
257
|
+
"referenceType",
|
|
258
|
+
"alt"
|
|
259
|
+
],
|
|
260
|
+
definition: [
|
|
261
|
+
"identifier",
|
|
262
|
+
"label",
|
|
263
|
+
"url",
|
|
264
|
+
"title"
|
|
265
|
+
],
|
|
266
|
+
footnoteReference: ["identifier", "label"],
|
|
267
|
+
footnoteDefinition: ["identifier", "label"],
|
|
268
|
+
list: [
|
|
269
|
+
"ordered",
|
|
270
|
+
"start",
|
|
271
|
+
"spread"
|
|
272
|
+
],
|
|
273
|
+
listItem: ["spread", "checked"],
|
|
274
|
+
table: ["align"]
|
|
275
|
+
};
|
|
276
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
277
|
+
const completePoint = (value) => isRecord(value) && typeof value.line === "number" && typeof value.column === "number" && typeof value.offset === "number";
|
|
278
|
+
const normalizePosition = (value) => {
|
|
279
|
+
if (isRecord(value) && completePoint(value.start) && completePoint(value.end)) {
|
|
280
|
+
const start = value.start;
|
|
281
|
+
const end = value.end;
|
|
282
|
+
return {
|
|
283
|
+
start: {
|
|
284
|
+
line: start.line,
|
|
285
|
+
column: start.column,
|
|
286
|
+
offset: start.offset
|
|
287
|
+
},
|
|
288
|
+
end: {
|
|
289
|
+
line: end.line,
|
|
290
|
+
column: end.column,
|
|
291
|
+
offset: end.offset
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return sentinelPosition;
|
|
296
|
+
};
|
|
297
|
+
const normalizeNode = (value) => {
|
|
298
|
+
if (!isRecord(value) || typeof value.type !== "string") return value;
|
|
299
|
+
const type = value.type;
|
|
300
|
+
const format = frontmatterTypes.get(type);
|
|
301
|
+
if (format !== void 0) return {
|
|
302
|
+
type: "frontmatter",
|
|
303
|
+
format,
|
|
304
|
+
value: value.value,
|
|
305
|
+
position: normalizePosition(value.position)
|
|
306
|
+
};
|
|
307
|
+
const admitted = admittedFields[type];
|
|
308
|
+
if (admitted === void 0) return value;
|
|
309
|
+
const normalized = { type };
|
|
310
|
+
for (const field of admitted) {
|
|
311
|
+
const raw = value[field];
|
|
312
|
+
if (raw !== void 0 && raw !== null) normalized[field] = raw;
|
|
313
|
+
}
|
|
314
|
+
if (type === "code" && typeof normalized.value === "string" && normalized.value !== "") {
|
|
315
|
+
const code = normalized.value;
|
|
316
|
+
normalized.value = code.endsWith("\n") ? code : `${code}\n`;
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(value.children)) normalized.children = value.children.map(normalizeNode);
|
|
319
|
+
normalized.position = normalizePosition(value.position);
|
|
320
|
+
return normalized;
|
|
321
|
+
};
|
|
322
|
+
const decodeRoot = Schema.decodeUnknownResult(Root);
|
|
323
|
+
/**
|
|
324
|
+
* The mdast projection facade — the remark-ecosystem interop boundary.
|
|
325
|
+
*
|
|
326
|
+
* @public
|
|
327
|
+
*/
|
|
328
|
+
var Mdast = class Mdast {
|
|
329
|
+
/**
|
|
330
|
+
* Project a parsed {@link Root} to plain mdast JSON.
|
|
331
|
+
*
|
|
332
|
+
* @remarks
|
|
333
|
+
* Total and pure: every tree the parser or {@link Mdast.fromMdast}
|
|
334
|
+
* produces projects without failure. Fidelity extras are stripped;
|
|
335
|
+
* optional mdast fields are spelled the way `mdast-util-from-markdown`
|
|
336
|
+
* spells them, so the output deep-equals the reference utility's trees
|
|
337
|
+
* (the vendored interop corpus pins this). The frontmatter capture
|
|
338
|
+
* projects to a `yaml`/`toml`/`json` literal node.
|
|
339
|
+
*
|
|
340
|
+
* @param root - The parsed document tree.
|
|
341
|
+
* @returns A plain mdast `root` object with unist positions.
|
|
342
|
+
*/
|
|
343
|
+
static toMdast(root) {
|
|
344
|
+
return projectNode(root);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Decode foreign plain mdast into the package's node classes,
|
|
348
|
+
* synchronously, as a `Result`. The pure primitive twin of
|
|
349
|
+
* {@link Mdast.fromMdast}.
|
|
350
|
+
*
|
|
351
|
+
* @remarks
|
|
352
|
+
* unist makes positions optional and this package's classes require
|
|
353
|
+
* them, so missing or incomplete positions are synthesized as the
|
|
354
|
+
* zero-width sentinel (line 1, column 1, offset 0) — clearly synthetic
|
|
355
|
+
* and inert for rendering. Trees carrying sentinel positions serve
|
|
356
|
+
* tree-level workflows (stringify, the visitor, projection back out),
|
|
357
|
+
* not offset-splice editing, whose offsets must come from a real parse.
|
|
358
|
+
* `null` values on optional fields normalize to absence per unist's
|
|
359
|
+
* null-equals-absent convention; foreign `data` and other unrecognized
|
|
360
|
+
* fields are dropped at the boundary; `yaml`/`toml`/`json` literal nodes
|
|
361
|
+
* decode into the {@link Frontmatter} capture. Unknown node types fail
|
|
362
|
+
* typed.
|
|
363
|
+
*
|
|
364
|
+
* @param input - A plain mdast tree, typically a `root`.
|
|
365
|
+
* @returns A `Result` succeeding with the decoded {@link Root}, or
|
|
366
|
+
* failing with {@link MdastDecodeError} carrying the structured issue.
|
|
367
|
+
*/
|
|
368
|
+
static fromMdastResult(input) {
|
|
369
|
+
return Result.mapError(decodeRoot(normalizeNode(input)), (error) => new MdastDecodeError({ issue: error.issue }));
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Decode foreign plain mdast into the package's node classes. Defined in
|
|
373
|
+
* terms of {@link Mdast.fromMdastResult} — synchronous callers can use
|
|
374
|
+
* that variant directly.
|
|
375
|
+
*
|
|
376
|
+
* @param input - A plain mdast tree, typically a `root`.
|
|
377
|
+
* @returns An `Effect` that succeeds with the decoded {@link Root}, or
|
|
378
|
+
* fails with {@link MdastDecodeError}.
|
|
379
|
+
*/
|
|
380
|
+
static fromMdast = Effect.fn("Mdast.fromMdast")((input) => Effect.fromResult(Mdast.fromMdastResult(input)));
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
//#endregion
|
|
384
|
+
export { Mdast, MdastDecodeError };
|