@effected/markdown 0.2.5 → 0.3.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/MarkdownDocument.js +127 -4
- package/index.d.ts +108 -2
- package/index.js +2 -2
- package/package.json +1 -1
package/MarkdownDocument.js
CHANGED
|
@@ -6,6 +6,80 @@ import { parsePassResult } from "./Markdown.js";
|
|
|
6
6
|
import { Effect, Result, Schema } from "effect";
|
|
7
7
|
|
|
8
8
|
//#region src/MarkdownDocument.ts
|
|
9
|
+
/**
|
|
10
|
+
* A heading-delimited span from {@link MarkdownDocument.sections}: the
|
|
11
|
+
* heading, its depth, the source `range` the section occupies and the
|
|
12
|
+
* root-level blocks that follow the heading inside it.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* A section runs from its heading's start offset to the start of the next
|
|
16
|
+
* root-level heading of equal or shallower depth, or to the end of the
|
|
17
|
+
* source. Deeper headings nest inside, so a parent section's `range` and
|
|
18
|
+
* `children` include its subsections — the list is flat, in document order,
|
|
19
|
+
* with `depth` carrying the hierarchy.
|
|
20
|
+
*
|
|
21
|
+
* Instances come from {@link MarkdownDocument.sections} and the two finders;
|
|
22
|
+
* there is no reason to construct one directly.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
var DocumentSection = class {
|
|
27
|
+
/** The heading that opens the section. */
|
|
28
|
+
heading;
|
|
29
|
+
/** The heading's depth, and the section's. */
|
|
30
|
+
depth;
|
|
31
|
+
/** The whole section including its heading — spliceable by `MarkdownEdit`. */
|
|
32
|
+
range;
|
|
33
|
+
/** The root-level blocks after the heading, subsections included. */
|
|
34
|
+
children;
|
|
35
|
+
/**
|
|
36
|
+
* The heading's plain-text content.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* The same derivation as {@link DocumentHeading.text}: text and inline-code
|
|
40
|
+
* values concatenated, image `alt` where present, a hard break as one space,
|
|
41
|
+
* nothing for raw HTML or footnote references. So `## **1.2.3**` reads
|
|
42
|
+
* `1.2.3`.
|
|
43
|
+
*/
|
|
44
|
+
text;
|
|
45
|
+
/**
|
|
46
|
+
* The section WITHOUT its heading: from the heading's end offset to the
|
|
47
|
+
* section's end.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* This is the span a "give me the release notes under this version" reader
|
|
51
|
+
* wants, and the one that is fiddly to derive by hand.
|
|
52
|
+
*/
|
|
53
|
+
bodyRange;
|
|
54
|
+
/** The document source these ranges index into. */
|
|
55
|
+
#documentSource;
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.heading = options.heading;
|
|
58
|
+
this.depth = options.depth;
|
|
59
|
+
this.range = options.range;
|
|
60
|
+
this.children = options.children;
|
|
61
|
+
this.text = options.text;
|
|
62
|
+
this.bodyRange = options.bodyRange;
|
|
63
|
+
this.#documentSource = options.documentSource;
|
|
64
|
+
}
|
|
65
|
+
/** The section's source text, heading included. */
|
|
66
|
+
get source() {
|
|
67
|
+
return this.#documentSource.slice(this.range.offset, this.range.offset + this.range.length);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The section's source text without its heading.
|
|
71
|
+
*
|
|
72
|
+
* @remarks
|
|
73
|
+
* **Untrimmed, deliberately.** It is exactly the bytes {@link
|
|
74
|
+
* DocumentSection.bodyRange} describes, so the string and the offsets can
|
|
75
|
+
* never disagree — a package that publishes a span's offsets and then hands
|
|
76
|
+
* back a different span is lying about it. Callers that want the tidy form
|
|
77
|
+
* write `.body.trim()`, visibly.
|
|
78
|
+
*/
|
|
79
|
+
get body() {
|
|
80
|
+
return this.#documentSource.slice(this.bodyRange.offset, this.bodyRange.offset + this.bodyRange.length);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
9
83
|
const walkTree = (node, depth, visit) => {
|
|
10
84
|
if (depth > 256) throw new Error(`NestingDepthExceeded: limit ${256} exceeded while walking the document tree`);
|
|
11
85
|
visit(node);
|
|
@@ -145,19 +219,68 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
145
219
|
}
|
|
146
220
|
}
|
|
147
221
|
const start = block.position.start.offset;
|
|
148
|
-
|
|
222
|
+
const bodyStart = block.position.end.offset;
|
|
223
|
+
sections.push(new DocumentSection({
|
|
149
224
|
heading: block,
|
|
150
225
|
depth: block.depth,
|
|
151
226
|
range: MarkdownRange.make({
|
|
152
227
|
offset: start,
|
|
153
228
|
length: boundary - start
|
|
154
229
|
}),
|
|
155
|
-
children: blocks.slice(index + 1, end).filter((child) => child.type !== "frontmatter")
|
|
156
|
-
|
|
230
|
+
children: blocks.slice(index + 1, end).filter((child) => child.type !== "frontmatter"),
|
|
231
|
+
text: phrasingText(block.children),
|
|
232
|
+
bodyRange: MarkdownRange.make({
|
|
233
|
+
offset: bodyStart,
|
|
234
|
+
length: Math.max(0, boundary - bodyStart)
|
|
235
|
+
}),
|
|
236
|
+
documentSource: this.source
|
|
237
|
+
}));
|
|
157
238
|
}
|
|
158
239
|
return sections;
|
|
159
240
|
}
|
|
160
241
|
/**
|
|
242
|
+
* The first section in document order, optionally restricted by heading
|
|
243
|
+
* depth.
|
|
244
|
+
*
|
|
245
|
+
* @remarks
|
|
246
|
+
* `firstSection({ depth: 2 })` is "the newest entry" for a changelog whose
|
|
247
|
+
* releases are H2s under an H1 title. Like the other navigation accessors
|
|
248
|
+
* this recomputes {@link MarkdownDocument.sections}; a caller scanning one
|
|
249
|
+
* document repeatedly should bind `document.sections` once instead.
|
|
250
|
+
*
|
|
251
|
+
* @param options - Depth restriction.
|
|
252
|
+
* @returns The first matching section, or `undefined`.
|
|
253
|
+
*/
|
|
254
|
+
firstSection(options) {
|
|
255
|
+
const depth = options?.depth;
|
|
256
|
+
return this.sections.find((section) => depth === void 0 || section.depth === depth);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The first section whose heading matches, in document order.
|
|
260
|
+
*
|
|
261
|
+
* @remarks
|
|
262
|
+
* A **string matches the trimmed heading text exactly** — never a substring.
|
|
263
|
+
* That is a deliberate refusal of the obvious convenience: `"1.2.3"` would
|
|
264
|
+
* otherwise match a `## 1.2.30` heading, and in a changelog (where `1.2.30`
|
|
265
|
+
* sorts newer and therefore sits *earlier*) that silently publishes the
|
|
266
|
+
* wrong release. A `RegExp` is tested against the same trimmed text, and its
|
|
267
|
+
* `lastIndex` is reset first so a `/g/` pattern cannot skip sections. A
|
|
268
|
+
* predicate receives the raw {@link DocumentSection.text} plus the section
|
|
269
|
+
* itself and decides freely.
|
|
270
|
+
*
|
|
271
|
+
* @param match - A string, a RegExp, or a predicate.
|
|
272
|
+
* @param options - Depth restriction, applied before matching.
|
|
273
|
+
* @returns The first matching section, or `undefined`.
|
|
274
|
+
*/
|
|
275
|
+
sectionByHeading(match, options) {
|
|
276
|
+
const depth = options?.depth;
|
|
277
|
+
const matches = typeof match === "string" ? (section) => section.text.trim() === match : match instanceof RegExp ? (section) => {
|
|
278
|
+
match.lastIndex = 0;
|
|
279
|
+
return match.test(section.text.trim());
|
|
280
|
+
} : (section) => match(section.text, section);
|
|
281
|
+
return this.sections.find((section) => (depth === void 0 || section.depth === depth) && matches(section));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
161
284
|
* Every link-bearing node in the document, in document order: links,
|
|
162
285
|
* images, definitions, and the reference forms resolved through the
|
|
163
286
|
* definition index.
|
|
@@ -245,4 +368,4 @@ var MarkdownDocument = class MarkdownDocument extends Schema.Class("MarkdownDocu
|
|
|
245
368
|
};
|
|
246
369
|
|
|
247
370
|
//#endregion
|
|
248
|
-
export { MarkdownDocument };
|
|
371
|
+
export { DocumentSection, MarkdownDocument };
|
package/index.d.ts
CHANGED
|
@@ -1049,14 +1049,88 @@ interface DocumentHeading {
|
|
|
1049
1049
|
* `children` include its subsections — the list is flat, in document order,
|
|
1050
1050
|
* with `depth` carrying the hierarchy.
|
|
1051
1051
|
*
|
|
1052
|
+
* Instances come from {@link MarkdownDocument.sections} and the two finders;
|
|
1053
|
+
* there is no reason to construct one directly.
|
|
1054
|
+
*
|
|
1052
1055
|
* @public
|
|
1053
1056
|
*/
|
|
1054
|
-
|
|
1057
|
+
declare class DocumentSection {
|
|
1058
|
+
#private;
|
|
1059
|
+
/** The heading that opens the section. */
|
|
1055
1060
|
readonly heading: Heading;
|
|
1061
|
+
/** The heading's depth, and the section's. */
|
|
1056
1062
|
readonly depth: HeadingDepth;
|
|
1063
|
+
/** The whole section including its heading — spliceable by `MarkdownEdit`. */
|
|
1057
1064
|
readonly range: MarkdownRange;
|
|
1065
|
+
/** The root-level blocks after the heading, subsections included. */
|
|
1058
1066
|
readonly children: ReadonlyArray<FlowContent>;
|
|
1067
|
+
/**
|
|
1068
|
+
* The heading's plain-text content.
|
|
1069
|
+
*
|
|
1070
|
+
* @remarks
|
|
1071
|
+
* The same derivation as {@link DocumentHeading.text}: text and inline-code
|
|
1072
|
+
* values concatenated, image `alt` where present, a hard break as one space,
|
|
1073
|
+
* nothing for raw HTML or footnote references. So `## **1.2.3**` reads
|
|
1074
|
+
* `1.2.3`.
|
|
1075
|
+
*/
|
|
1076
|
+
readonly text: string;
|
|
1077
|
+
/**
|
|
1078
|
+
* The section WITHOUT its heading: from the heading's end offset to the
|
|
1079
|
+
* section's end.
|
|
1080
|
+
*
|
|
1081
|
+
* @remarks
|
|
1082
|
+
* This is the span a "give me the release notes under this version" reader
|
|
1083
|
+
* wants, and the one that is fiddly to derive by hand.
|
|
1084
|
+
*/
|
|
1085
|
+
readonly bodyRange: MarkdownRange;
|
|
1086
|
+
constructor(options: {
|
|
1087
|
+
readonly heading: Heading;
|
|
1088
|
+
readonly depth: HeadingDepth;
|
|
1089
|
+
readonly range: MarkdownRange;
|
|
1090
|
+
readonly children: ReadonlyArray<FlowContent>;
|
|
1091
|
+
readonly text: string;
|
|
1092
|
+
readonly bodyRange: MarkdownRange;
|
|
1093
|
+
readonly documentSource: string;
|
|
1094
|
+
});
|
|
1095
|
+
/** The section's source text, heading included. */
|
|
1096
|
+
get source(): string;
|
|
1097
|
+
/**
|
|
1098
|
+
* The section's source text without its heading.
|
|
1099
|
+
*
|
|
1100
|
+
* @remarks
|
|
1101
|
+
* **Untrimmed, deliberately.** It is exactly the bytes {@link
|
|
1102
|
+
* DocumentSection.bodyRange} describes, so the string and the offsets can
|
|
1103
|
+
* never disagree — a package that publishes a span's offsets and then hands
|
|
1104
|
+
* back a different span is lying about it. Callers that want the tidy form
|
|
1105
|
+
* write `.body.trim()`, visibly.
|
|
1106
|
+
*/
|
|
1107
|
+
get body(): string;
|
|
1059
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Narrowing options for {@link MarkdownDocument.firstSection} and
|
|
1111
|
+
* {@link MarkdownDocument.sectionByHeading}.
|
|
1112
|
+
*
|
|
1113
|
+
* @public
|
|
1114
|
+
*/
|
|
1115
|
+
interface SectionQueryOptions {
|
|
1116
|
+
/**
|
|
1117
|
+
* Restrict to sections opened by a heading of exactly this depth.
|
|
1118
|
+
*
|
|
1119
|
+
* @remarks
|
|
1120
|
+
* Equality, not a maximum: `{ depth: 2 }` is "the H2 sections", which is the
|
|
1121
|
+
* changelog idiom. A depth *range* is a different question and belongs in a
|
|
1122
|
+
* predicate.
|
|
1123
|
+
*/
|
|
1124
|
+
readonly depth?: HeadingDepth;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* How {@link MarkdownDocument.sectionByHeading} decides a section matches: an
|
|
1128
|
+
* exact trimmed-text string, a `RegExp`, or a predicate over the text and the
|
|
1129
|
+
* section.
|
|
1130
|
+
*
|
|
1131
|
+
* @public
|
|
1132
|
+
*/
|
|
1133
|
+
type SectionHeadingMatch = string | RegExp | ((text: string, section: DocumentSection) => boolean);
|
|
1060
1134
|
/**
|
|
1061
1135
|
* The node types {@link MarkdownDocument.links} collects: the nodes that
|
|
1062
1136
|
* carry an outbound URL themselves ({@link Link}, {@link Image},
|
|
@@ -1154,6 +1228,38 @@ declare class MarkdownDocument extends MarkdownDocument_base {
|
|
|
1154
1228
|
* boundary heading's start, or to the end of the source.
|
|
1155
1229
|
*/
|
|
1156
1230
|
get sections(): ReadonlyArray<DocumentSection>;
|
|
1231
|
+
/**
|
|
1232
|
+
* The first section in document order, optionally restricted by heading
|
|
1233
|
+
* depth.
|
|
1234
|
+
*
|
|
1235
|
+
* @remarks
|
|
1236
|
+
* `firstSection({ depth: 2 })` is "the newest entry" for a changelog whose
|
|
1237
|
+
* releases are H2s under an H1 title. Like the other navigation accessors
|
|
1238
|
+
* this recomputes {@link MarkdownDocument.sections}; a caller scanning one
|
|
1239
|
+
* document repeatedly should bind `document.sections` once instead.
|
|
1240
|
+
*
|
|
1241
|
+
* @param options - Depth restriction.
|
|
1242
|
+
* @returns The first matching section, or `undefined`.
|
|
1243
|
+
*/
|
|
1244
|
+
firstSection(options?: SectionQueryOptions): DocumentSection | undefined;
|
|
1245
|
+
/**
|
|
1246
|
+
* The first section whose heading matches, in document order.
|
|
1247
|
+
*
|
|
1248
|
+
* @remarks
|
|
1249
|
+
* A **string matches the trimmed heading text exactly** — never a substring.
|
|
1250
|
+
* That is a deliberate refusal of the obvious convenience: `"1.2.3"` would
|
|
1251
|
+
* otherwise match a `## 1.2.30` heading, and in a changelog (where `1.2.30`
|
|
1252
|
+
* sorts newer and therefore sits *earlier*) that silently publishes the
|
|
1253
|
+
* wrong release. A `RegExp` is tested against the same trimmed text, and its
|
|
1254
|
+
* `lastIndex` is reset first so a `/g/` pattern cannot skip sections. A
|
|
1255
|
+
* predicate receives the raw {@link DocumentSection.text} plus the section
|
|
1256
|
+
* itself and decides freely.
|
|
1257
|
+
*
|
|
1258
|
+
* @param match - A string, a RegExp, or a predicate.
|
|
1259
|
+
* @param options - Depth restriction, applied before matching.
|
|
1260
|
+
* @returns The first matching section, or `undefined`.
|
|
1261
|
+
*/
|
|
1262
|
+
sectionByHeading(match: SectionHeadingMatch, options?: SectionQueryOptions): DocumentSection | undefined;
|
|
1157
1263
|
/**
|
|
1158
1264
|
* Every link-bearing node in the document, in document order: links,
|
|
1159
1265
|
* images, definitions, and the reference forms resolved through the
|
|
@@ -2186,5 +2292,5 @@ declare const TomlFrontmatter: FrontmatterCodec;
|
|
|
2186
2292
|
*/
|
|
2187
2293
|
declare const YamlFrontmatter: FrontmatterCodec;
|
|
2188
2294
|
//#endregion
|
|
2189
|
-
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, type DocumentHeading, type DocumentLink,
|
|
2295
|
+
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, type DocumentHeading, type DocumentLink, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, type FrontmatterCodec, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, type FrontmatterResolveError, type FrontmatterSchemaError, type FrontmatterSchemaResolver, FrontmatterValidationError, type FrontmatterWriteError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, type LinkBearingNode, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, type MarkdownNodeOfType, type MarkdownNodeType, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, type MarkdownPath, MarkdownRange, type MarkdownRangeLike, type MarkdownSegment, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, type MdastNode, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, type SectionHeadingMatch, type SectionQueryOptions, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
2190
2296
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, Sc
|
|
|
5
5
|
import { JsonFrontmatter } from "./JsonFrontmatter.js";
|
|
6
6
|
import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic.js";
|
|
7
7
|
import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
|
|
8
|
-
import { MarkdownDocument } from "./MarkdownDocument.js";
|
|
8
|
+
import { DocumentSection, MarkdownDocument } from "./MarkdownDocument.js";
|
|
9
9
|
import { MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
|
|
10
10
|
import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
|
|
11
11
|
import { Mdast, MdastDecodeError } from "./Mdast.js";
|
|
12
12
|
import { TomlFrontmatter } from "./TomlFrontmatter.js";
|
|
13
13
|
import { YamlFrontmatter } from "./YamlFrontmatter.js";
|
|
14
14
|
|
|
15
|
-
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|
|
15
|
+
export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, DocumentSection, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
|