@markii/core 0.2.0 → 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/dist/corpus.js +1 -1
- package/dist/frontmatter.d.ts +50 -0
- package/dist/frontmatter.js +178 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/parse.d.ts +12 -0
- package/dist/parse.js +14 -0
- package/dist/scripts.d.ts +5 -5
- package/dist/scripts.js +4 -4
- package/dist/to-hast.js +12 -0
- package/package.json +2 -1
package/dist/corpus.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, join } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
/**
|
|
5
5
|
* The language-agnostic conformance corpus: `*.mk.md` inputs plus
|
|
6
|
-
* expected-AST `*.json` siblings, living at the repo root (see
|
|
6
|
+
* expected-AST `*.json` siblings, living at the repo root (see docs/spec.md
|
|
7
7
|
* §13). This module is deliberately parser-agnostic — it only
|
|
8
8
|
* loads/compares corpus data — so it's usable from any package's tests (or
|
|
9
9
|
* a future non-JS implementation's tooling), not just `@markii/core`'s own
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* A document's leading YAML frontmatter block, as far as this package cares
|
|
4
|
+
* about it: the raw text between the `---` fences, plus the one
|
|
5
|
+
* format-defined key (`uses`) when it is written in one of the two simple
|
|
6
|
+
* list forms this module understands.
|
|
7
|
+
*
|
|
8
|
+
* `raw` is the block's exact source text, unparsed — a host that genuinely
|
|
9
|
+
* needs arbitrary YAML metadata (its own `title`, `tags`, ...) brings its own
|
|
10
|
+
* YAML parser and reads this string. `@markii/core` deliberately does not:
|
|
11
|
+
* see `readUses` below.
|
|
12
|
+
*/
|
|
13
|
+
export interface Frontmatter {
|
|
14
|
+
/** The raw YAML text between the `---` fences (no fences, no trailing newline). */
|
|
15
|
+
raw: string;
|
|
16
|
+
/**
|
|
17
|
+
* Pack names from a `uses:` list written in one of the two supported
|
|
18
|
+
* forms, or `undefined` when the key is absent, is written in any other
|
|
19
|
+
* shape, or contains anything that is not a plain name. An empty list
|
|
20
|
+
* (`uses: []`) is `[]` — "declared, and it names no packs" — which is
|
|
21
|
+
* distinguishable from `undefined` ("no usable declaration").
|
|
22
|
+
*/
|
|
23
|
+
uses?: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The document's leading YAML frontmatter, or `undefined` when it has none.
|
|
27
|
+
*
|
|
28
|
+
* Accepts either the source text or an already-parsed tree, so a caller that
|
|
29
|
+
* has parsed the document already (the common case for a renderer) pays no
|
|
30
|
+
* second parse. Recognition is the PARSER's, not this module's: frontmatter
|
|
31
|
+
* is whatever `parse` produced as a leading `yaml` node, so the accessor and
|
|
32
|
+
* the renderer can never disagree about whether a `---` block at the top of
|
|
33
|
+
* a file was metadata or a thematic break.
|
|
34
|
+
*
|
|
35
|
+
* Never throws — `uses` simply degrades to `undefined` for anything beyond
|
|
36
|
+
* the two list forms `readUses` documents. A tree that did not come from
|
|
37
|
+
* this package's `parse` (a host that transforms the AST, or hand-builds
|
|
38
|
+
* one) is treated the same way: a leading node that merely CLAIMS to be
|
|
39
|
+
* `yaml` without carrying string content is no frontmatter at all, rather
|
|
40
|
+
* than a `raw.split` on a number.
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractFrontmatter(source: string | Root): Frontmatter | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Convenience wrapper over `extractFrontmatter`: just the `uses` list, or
|
|
45
|
+
* `undefined` when the document has no frontmatter, no `uses:` key, or a
|
|
46
|
+
* `uses:` value outside the two supported list forms. `uses` semantics
|
|
47
|
+
* (surfacing "pack not installed" in a host's UI) live with the host — this
|
|
48
|
+
* is only the reader.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractFrontmatterUses(source: string | Root): string[] | undefined;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { parse } from './parse.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one format-defined frontmatter key (docs/format.md, docs/packs.md), as
|
|
4
|
+
* a top-level `uses:` line: the key at column 0 (so a `uses:` nested under
|
|
5
|
+
* some other mapping is deliberately invisible to this reader), optional
|
|
6
|
+
* spacing, then whatever follows the colon on the same line.
|
|
7
|
+
*/
|
|
8
|
+
const USES_LINE = /^uses[ \t]*:(.*)$/;
|
|
9
|
+
/** One block-sequence item line: `- name`, capturing its indent and value. */
|
|
10
|
+
const LIST_ITEM_LINE = /^([ \t]*)-[ \t]+(.*)$/;
|
|
11
|
+
/**
|
|
12
|
+
* What a pack name may look like once quotes are stripped. Intentionally
|
|
13
|
+
* narrow: alphanumerics, `-`, `_`, `.` only (docs/packs.md — a namespace is
|
|
14
|
+
* a plain word, joined to a component name with `-` or `_`). Anything else —
|
|
15
|
+
* whitespace, `:`, `#`, brackets, braces, commas, a stray quote — means the
|
|
16
|
+
* value is not the simple scalar this reader claims to understand, so the
|
|
17
|
+
* whole list degrades to `undefined` rather than being half-read.
|
|
18
|
+
*
|
|
19
|
+
* A name like `__proto__` or `constructor` passes, and that is deliberate:
|
|
20
|
+
* this reader returns STRINGS in an array, never object keys, so a
|
|
21
|
+
* prototype-flavored name is ordinary data here. The registry lookups that
|
|
22
|
+
* eventually consume such a name are themselves null-prototype and
|
|
23
|
+
* `Object.hasOwn`-guarded (`@markii/react`'s `registry.ts`), which is where
|
|
24
|
+
* that class of defense belongs — not in a name filter that would have to
|
|
25
|
+
* guess every hostile spelling.
|
|
26
|
+
*/
|
|
27
|
+
const PACK_NAME = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
|
|
28
|
+
/**
|
|
29
|
+
* Reads one flow-sequence or block-sequence item: trims it, strips ONE pair
|
|
30
|
+
* of matching surrounding quotes (`'a'` / `"a"` — YAML's two quoting styles,
|
|
31
|
+
* used here only to unwrap a name, never to interpret escapes), and returns
|
|
32
|
+
* it only if what remains is a plain pack name. Returns `undefined` for
|
|
33
|
+
* anything else, including an opening quote with no matching close.
|
|
34
|
+
*/
|
|
35
|
+
function readItem(text) {
|
|
36
|
+
const trimmed = text.trim();
|
|
37
|
+
if (trimmed === '')
|
|
38
|
+
return undefined;
|
|
39
|
+
const quote = trimmed[0];
|
|
40
|
+
let value = trimmed;
|
|
41
|
+
if (quote === '"' || quote === "'") {
|
|
42
|
+
if (trimmed.length < 2 || !trimmed.endsWith(quote))
|
|
43
|
+
return undefined;
|
|
44
|
+
value = trimmed.slice(1, -1);
|
|
45
|
+
}
|
|
46
|
+
return PACK_NAME.test(value) ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Maps `readItem` over every raw item, failing the WHOLE list if any item fails. */
|
|
49
|
+
function readItems(rawItems) {
|
|
50
|
+
const values = [];
|
|
51
|
+
for (const rawItem of rawItems) {
|
|
52
|
+
const value = readItem(rawItem);
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
values.push(value);
|
|
56
|
+
}
|
|
57
|
+
return values;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reads the flow form — `uses: [a, b]` — from the text that follows the
|
|
61
|
+
* colon. The text must be exactly a bracketed sequence: a trailing comment
|
|
62
|
+
* (`uses: [a] # note`) or any other suffix is NOT this form and yields
|
|
63
|
+
* `undefined`, since recognizing it would mean tokenizing YAML rather than
|
|
64
|
+
* matching one fixed shape. `[]` yields the empty list.
|
|
65
|
+
*/
|
|
66
|
+
function readFlowSequence(text) {
|
|
67
|
+
if (!text.startsWith('[') || !text.endsWith(']'))
|
|
68
|
+
return undefined;
|
|
69
|
+
const inner = text.slice(1, -1).trim();
|
|
70
|
+
if (inner === '')
|
|
71
|
+
return [];
|
|
72
|
+
return readItems(inner.split(','));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reads the block form — a bare `uses:` followed by `- name` lines — from
|
|
76
|
+
* the lines after the key. Items must be contiguous (the first line that is
|
|
77
|
+
* not an item ends the sequence, blank lines included) and share one indent,
|
|
78
|
+
* which is what keeps a differently-indented continuation of some richer
|
|
79
|
+
* YAML structure from being mistaken for a flat list. No items, or a
|
|
80
|
+
* ragged/uneven sequence, yields `undefined`.
|
|
81
|
+
*/
|
|
82
|
+
function readBlockSequence(lines, start) {
|
|
83
|
+
const rawItems = [];
|
|
84
|
+
let indent;
|
|
85
|
+
for (const line of lines.slice(start)) {
|
|
86
|
+
const match = LIST_ITEM_LINE.exec(line);
|
|
87
|
+
const [, lineIndent, item] = match ?? [];
|
|
88
|
+
if (lineIndent === undefined || item === undefined)
|
|
89
|
+
break;
|
|
90
|
+
if (indent === undefined)
|
|
91
|
+
indent = lineIndent;
|
|
92
|
+
else if (lineIndent !== indent)
|
|
93
|
+
return undefined;
|
|
94
|
+
rawItems.push(item);
|
|
95
|
+
}
|
|
96
|
+
return rawItems.length > 0 ? readItems(rawItems) : undefined;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The hand-rolled `uses:` reader — the whole reason `@markii/core` needs no
|
|
100
|
+
* YAML library, exactly as `@markii/bundle`'s `manifest.ts` needs no schema
|
|
101
|
+
* library. It recognizes TWO shapes and nothing else:
|
|
102
|
+
*
|
|
103
|
+
* ```yaml
|
|
104
|
+
* uses: [ana, gh] # flow sequence
|
|
105
|
+
* uses: # block sequence
|
|
106
|
+
* - ana
|
|
107
|
+
* - gh
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* quotes and surrounding whitespace tolerated in both. Every other input —
|
|
111
|
+
* a `uses:` holding a scalar or a nested mapping, a duplicated `uses:` key,
|
|
112
|
+
* an indented (non-top-level) `uses:`, a value this reader cannot recognize,
|
|
113
|
+
* or frontmatter that is not valid YAML at all — returns `undefined`. It
|
|
114
|
+
* NEVER throws and never returns a partially-read list: `uses` is an
|
|
115
|
+
* informative hint (docs/packs.md — "this note uses pack `ana`, which is not
|
|
116
|
+
* installed"), so "I could not read this" and "there is nothing here" are
|
|
117
|
+
* the same, harmless answer, while a half-read list would produce a wrong
|
|
118
|
+
* warning about packs the note never named.
|
|
119
|
+
*/
|
|
120
|
+
function readUses(raw) {
|
|
121
|
+
const lines = raw.split(/\r?\n/);
|
|
122
|
+
let keyIndex = -1;
|
|
123
|
+
let rest = '';
|
|
124
|
+
for (const [index, line] of lines.entries()) {
|
|
125
|
+
const [, value] = USES_LINE.exec(line) ?? [];
|
|
126
|
+
if (value === undefined)
|
|
127
|
+
continue;
|
|
128
|
+
// A second top-level `uses:` makes the document ambiguous (YAML itself
|
|
129
|
+
// rejects duplicate keys); refuse to guess which one was meant.
|
|
130
|
+
if (keyIndex !== -1)
|
|
131
|
+
return undefined;
|
|
132
|
+
keyIndex = index;
|
|
133
|
+
rest = value;
|
|
134
|
+
}
|
|
135
|
+
if (keyIndex === -1)
|
|
136
|
+
return undefined;
|
|
137
|
+
const inline = rest.trim();
|
|
138
|
+
return inline === ''
|
|
139
|
+
? readBlockSequence(lines, keyIndex + 1)
|
|
140
|
+
: readFlowSequence(inline);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The document's leading YAML frontmatter, or `undefined` when it has none.
|
|
144
|
+
*
|
|
145
|
+
* Accepts either the source text or an already-parsed tree, so a caller that
|
|
146
|
+
* has parsed the document already (the common case for a renderer) pays no
|
|
147
|
+
* second parse. Recognition is the PARSER's, not this module's: frontmatter
|
|
148
|
+
* is whatever `parse` produced as a leading `yaml` node, so the accessor and
|
|
149
|
+
* the renderer can never disagree about whether a `---` block at the top of
|
|
150
|
+
* a file was metadata or a thematic break.
|
|
151
|
+
*
|
|
152
|
+
* Never throws — `uses` simply degrades to `undefined` for anything beyond
|
|
153
|
+
* the two list forms `readUses` documents. A tree that did not come from
|
|
154
|
+
* this package's `parse` (a host that transforms the AST, or hand-builds
|
|
155
|
+
* one) is treated the same way: a leading node that merely CLAIMS to be
|
|
156
|
+
* `yaml` without carrying string content is no frontmatter at all, rather
|
|
157
|
+
* than a `raw.split` on a number.
|
|
158
|
+
*/
|
|
159
|
+
export function extractFrontmatter(source) {
|
|
160
|
+
const tree = typeof source === 'string' ? parse(source) : source;
|
|
161
|
+
const first = tree.children[0];
|
|
162
|
+
if (first?.type !== 'yaml' || typeof first.value !== 'string') {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const raw = first.value;
|
|
166
|
+
const uses = readUses(raw);
|
|
167
|
+
return uses === undefined ? { raw } : { raw, uses };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Convenience wrapper over `extractFrontmatter`: just the `uses` list, or
|
|
171
|
+
* `undefined` when the document has no frontmatter, no `uses:` key, or a
|
|
172
|
+
* `uses:` value outside the two supported list forms. `uses` semantics
|
|
173
|
+
* (surfacing "pack not installed" in a host's UI) live with the host — this
|
|
174
|
+
* is only the reader.
|
|
175
|
+
*/
|
|
176
|
+
export function extractFrontmatterUses(source) {
|
|
177
|
+
return extractFrontmatter(source)?.uses;
|
|
178
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { parse } from './parse.js';
|
|
2
|
+
export { extractFrontmatter, extractFrontmatterUses, type Frontmatter, } from './frontmatter.js';
|
|
2
3
|
export { toHast, nodeToHast, isSafeUrl, type MarkNode } from './to-hast.js';
|
|
3
4
|
export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, type ScriptBlock, } from './scripts.js';
|
package/dist/index.js
CHANGED
|
@@ -5,5 +5,6 @@
|
|
|
5
5
|
// via `@markii/react`) never has to reason about Node built-ins reachable from
|
|
6
6
|
// its entry point.
|
|
7
7
|
export { parse } from './parse.js';
|
|
8
|
+
export { extractFrontmatter, extractFrontmatterUses, } from './frontmatter.js';
|
|
8
9
|
export { toHast, nodeToHast, isSafeUrl } from './to-hast.js';
|
|
9
10
|
export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, } from './scripts.js';
|
package/dist/parse.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Root } from 'mdast';
|
|
|
2
2
|
/**
|
|
3
3
|
* Parses Mark text into an mdast AST. CommonMark plus GitHub-Flavored
|
|
4
4
|
* Markdown (`remark-gfm`: tables, task lists, strikethrough, autolinks) plus
|
|
5
|
+
* optional leading YAML frontmatter (`remark-frontmatter`) plus
|
|
5
6
|
* the generic directive grammar (`remark-directive`) — nothing more. GFM and
|
|
6
7
|
* directives are independent micromark syntax extensions (GFM extends the
|
|
7
8
|
* table / list-item / inline grammar; directives add their own `:`/`::`/`:::`
|
|
@@ -13,5 +14,16 @@ import type { Root } from 'mdast';
|
|
|
13
14
|
* and children; interpreting a name is the renderer's job; GFM nodes are
|
|
14
15
|
* standard mdast — `table`, `listItem.checked`, `delete` — needing no
|
|
15
16
|
* component/registry coupling at all).
|
|
17
|
+
*
|
|
18
|
+
* Frontmatter is the YAML preset only (`---` fences; the TOML `+++` form is
|
|
19
|
+
* deliberately not enabled) and, per micromark's frontmatter extension, is
|
|
20
|
+
* recognized ONLY as the very first construct of the document. Everywhere
|
|
21
|
+
* else `---` keeps its ordinary CommonMark meaning — a thematic break, or a
|
|
22
|
+
* setext heading underline — and an unterminated opening `---` degrades to
|
|
23
|
+
* exactly that too, never to an error (conformance fixtures 19-23). The
|
|
24
|
+
* resulting `yaml` node carries the raw block text and nothing more: this
|
|
25
|
+
* module does not interpret YAML, and `@markii/core` depends on no YAML
|
|
26
|
+
* library. `frontmatter.ts`'s hand-rolled accessor reads the one
|
|
27
|
+
* format-defined key (`uses`) off that raw text for the simple list forms.
|
|
16
28
|
*/
|
|
17
29
|
export declare function parse(text: string): Root;
|
package/dist/parse.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { unified } from 'unified';
|
|
2
2
|
import remarkParse from 'remark-parse';
|
|
3
3
|
import remarkDirective from 'remark-directive';
|
|
4
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
4
5
|
import remarkGfm from 'remark-gfm';
|
|
5
6
|
/**
|
|
6
7
|
* Parses Mark text into an mdast AST. CommonMark plus GitHub-Flavored
|
|
7
8
|
* Markdown (`remark-gfm`: tables, task lists, strikethrough, autolinks) plus
|
|
9
|
+
* optional leading YAML frontmatter (`remark-frontmatter`) plus
|
|
8
10
|
* the generic directive grammar (`remark-directive`) — nothing more. GFM and
|
|
9
11
|
* directives are independent micromark syntax extensions (GFM extends the
|
|
10
12
|
* table / list-item / inline grammar; directives add their own `:`/`::`/`:::`
|
|
@@ -16,10 +18,22 @@ import remarkGfm from 'remark-gfm';
|
|
|
16
18
|
* and children; interpreting a name is the renderer's job; GFM nodes are
|
|
17
19
|
* standard mdast — `table`, `listItem.checked`, `delete` — needing no
|
|
18
20
|
* component/registry coupling at all).
|
|
21
|
+
*
|
|
22
|
+
* Frontmatter is the YAML preset only (`---` fences; the TOML `+++` form is
|
|
23
|
+
* deliberately not enabled) and, per micromark's frontmatter extension, is
|
|
24
|
+
* recognized ONLY as the very first construct of the document. Everywhere
|
|
25
|
+
* else `---` keeps its ordinary CommonMark meaning — a thematic break, or a
|
|
26
|
+
* setext heading underline — and an unterminated opening `---` degrades to
|
|
27
|
+
* exactly that too, never to an error (conformance fixtures 19-23). The
|
|
28
|
+
* resulting `yaml` node carries the raw block text and nothing more: this
|
|
29
|
+
* module does not interpret YAML, and `@markii/core` depends on no YAML
|
|
30
|
+
* library. `frontmatter.ts`'s hand-rolled accessor reads the one
|
|
31
|
+
* format-defined key (`uses`) off that raw text for the simple list forms.
|
|
19
32
|
*/
|
|
20
33
|
export function parse(text) {
|
|
21
34
|
const processor = unified()
|
|
22
35
|
.use(remarkParse)
|
|
36
|
+
.use(remarkFrontmatter)
|
|
23
37
|
.use(remarkDirective)
|
|
24
38
|
.use(remarkGfm);
|
|
25
39
|
return processor.parse(text);
|
package/dist/scripts.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Code, Root } from 'mdast';
|
|
2
2
|
/**
|
|
3
|
-
* Slice 1 of the scripting-usability layer (
|
|
3
|
+
* Slice 1 of the scripting-usability layer (docs/scripting.md): this module only
|
|
4
4
|
* *reads* script blocks out of an already-parsed AST — it never executes
|
|
5
5
|
* anything, imports no Lua/wasmoon, and knows nothing about a value store.
|
|
6
6
|
* A script block is, at the parse layer, an ordinary fenced code block (see
|
|
@@ -16,7 +16,7 @@ import type { Code, Root } from 'mdast';
|
|
|
16
16
|
* ` ```lua {src=scripts/etl.lua name=stars}` ` with an empty body"); `code`
|
|
17
17
|
* is still the fence's own body (empty for a `src=` reference).
|
|
18
18
|
*
|
|
19
|
-
* `publish`, when present, is always `true` —
|
|
19
|
+
* `publish`, when present, is always `true` — docs/scripting.md: "**Publishing is
|
|
20
20
|
* declarative**: the bare `publish` attribute on the script fence, no API to
|
|
21
21
|
* call." "Bare" is load-bearing: it is set ONLY when the fence spells the
|
|
22
22
|
* key with no `=value` at all (` ```lua {name=gh publish}` `, per
|
|
@@ -47,7 +47,7 @@ export interface ScriptBlock {
|
|
|
47
47
|
position?: Code['position'];
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Whether `name` is a legal script `name` (
|
|
50
|
+
* Whether `name` is a legal script `name` (docs/scripting.md). Dots are
|
|
51
51
|
* deliberately excluded from the charset — `data=`/`:value[]` read a dot in
|
|
52
52
|
* a name as path traversal (`repo.stars` means "field `stars` of value
|
|
53
53
|
* `repo`"), which would make a dotted *script* name unreachable from either
|
|
@@ -76,7 +76,7 @@ export declare function parseMetaAttributes(meta: string | null | undefined): Re
|
|
|
76
76
|
* Whether `key` appears in `meta`'s `{...}` attribute group written
|
|
77
77
|
* genuinely bare (`key` with no `=value` at all — none of the three value
|
|
78
78
|
* alternatives in `ATTRIBUTE_TOKEN` matched). This is the ONE shared
|
|
79
|
-
* mechanism for a bare-only boolean fence-meta attribute (
|
|
79
|
+
* mechanism for a bare-only boolean fence-meta attribute (docs/scripting.md:
|
|
80
80
|
* "Boolean fence-meta attributes (`publish`, the reference renderer's
|
|
81
81
|
* `open`) are bare-only: writing any value — `publish=true` no less than
|
|
82
82
|
* `publish=false` — is not the boolean form and counts as absent. Fail
|
|
@@ -105,7 +105,7 @@ export declare function isBareAttribute(meta: string | null | undefined, key: st
|
|
|
105
105
|
* Walks a parsed mdast `Root` and returns every script block, in document
|
|
106
106
|
* order. A `code` node is a script iff its `meta` carries a `{...}`
|
|
107
107
|
* attribute group with a non-empty `name` that also matches the script-name
|
|
108
|
-
* charset (
|
|
108
|
+
* charset (docs/scripting.md, `isValidScriptName`); a code block with no meta,
|
|
109
109
|
* meta with no `name`, or a `name` outside the charset (e.g. a dotted
|
|
110
110
|
* `repo.stars`) is ordinary code and is skipped — same display-only
|
|
111
111
|
* degradation either way, never an error and never a console message. Pure
|
package/dist/scripts.js
CHANGED
|
@@ -33,7 +33,7 @@ function findAttributeGroup(source) {
|
|
|
33
33
|
*/
|
|
34
34
|
const ATTRIBUTE_TOKEN = /([A-Za-z_][\w-]*)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
35
35
|
/**
|
|
36
|
-
* The full charset a script `name` may use (
|
|
36
|
+
* The full charset a script `name` may use (docs/scripting.md). Deliberately NOT
|
|
37
37
|
* `g`-flagged: a `g`-flagged `RegExp` carries `lastIndex` state across
|
|
38
38
|
* `.test()` calls (the exec-loop cursor from a previous match survives to
|
|
39
39
|
* the next call on the same instance), which silently makes alternating
|
|
@@ -44,7 +44,7 @@ const ATTRIBUTE_TOKEN = /([A-Za-z_][\w-]*)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
|
44
44
|
*/
|
|
45
45
|
const SCRIPT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
46
46
|
/**
|
|
47
|
-
* Whether `name` is a legal script `name` (
|
|
47
|
+
* Whether `name` is a legal script `name` (docs/scripting.md). Dots are
|
|
48
48
|
* deliberately excluded from the charset — `data=`/`:value[]` read a dot in
|
|
49
49
|
* a name as path traversal (`repo.stars` means "field `stars` of value
|
|
50
50
|
* `repo`"), which would make a dotted *script* name unreachable from either
|
|
@@ -91,7 +91,7 @@ export function parseMetaAttributes(meta) {
|
|
|
91
91
|
* Whether `key` appears in `meta`'s `{...}` attribute group written
|
|
92
92
|
* genuinely bare (`key` with no `=value` at all — none of the three value
|
|
93
93
|
* alternatives in `ATTRIBUTE_TOKEN` matched). This is the ONE shared
|
|
94
|
-
* mechanism for a bare-only boolean fence-meta attribute (
|
|
94
|
+
* mechanism for a bare-only boolean fence-meta attribute (docs/scripting.md:
|
|
95
95
|
* "Boolean fence-meta attributes (`publish`, the reference renderer's
|
|
96
96
|
* `open`) are bare-only: writing any value — `publish=true` no less than
|
|
97
97
|
* `publish=false` — is not the boolean form and counts as absent. Fail
|
|
@@ -138,7 +138,7 @@ export function isBareAttribute(meta, key) {
|
|
|
138
138
|
* Walks a parsed mdast `Root` and returns every script block, in document
|
|
139
139
|
* order. A `code` node is a script iff its `meta` carries a `{...}`
|
|
140
140
|
* attribute group with a non-empty `name` that also matches the script-name
|
|
141
|
-
* charset (
|
|
141
|
+
* charset (docs/scripting.md, `isValidScriptName`); a code block with no meta,
|
|
142
142
|
* meta with no `name`, or a `name` outside the charset (e.g. a dotted
|
|
143
143
|
* `repo.stars`) is ordinary code and is skipped — same display-only
|
|
144
144
|
* degradation either way, never an error and never a console message. Pure
|
package/dist/to-hast.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { unified } from 'unified';
|
|
2
2
|
import remarkParse from 'remark-parse';
|
|
3
3
|
import remarkDirective from 'remark-directive';
|
|
4
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
4
5
|
import remarkGfm from 'remark-gfm';
|
|
5
6
|
import remarkRehype from 'remark-rehype';
|
|
6
7
|
import { visit } from 'unist-util-visit';
|
|
@@ -156,10 +157,21 @@ function sanitizeUrls(tree) {
|
|
|
156
157
|
* immutable-processor model (`.use()` returns a new processor) and keeps
|
|
157
158
|
* each call's `.parse`/`.runSync` free of any state left over from a
|
|
158
159
|
* previous call.
|
|
160
|
+
*
|
|
161
|
+
* The plugin list mirrors `parse.ts` exactly (frontmatter, directives, GFM),
|
|
162
|
+
* so what `parse` accepts and what this pipeline renders can never disagree
|
|
163
|
+
* about the grammar. Frontmatter needs no tagging step of its own: a leading
|
|
164
|
+
* `---` block becomes an mdast `yaml` node, and `mdast-util-to-hast`'s
|
|
165
|
+
* default handler for `yaml` is its `ignore` handler, so the block is
|
|
166
|
+
* DROPPED from the hast tree rather than leaking into the rendered document
|
|
167
|
+
* as text (pinned by `to-hast.test.ts`, since that drop is a rendering
|
|
168
|
+
* guarantee of the format, not an incidental upstream default). Metadata is
|
|
169
|
+
* read from the AST via `frontmatter.ts`, never rendered.
|
|
159
170
|
*/
|
|
160
171
|
function createProcessor() {
|
|
161
172
|
return unified()
|
|
162
173
|
.use(remarkParse)
|
|
174
|
+
.use(remarkFrontmatter)
|
|
163
175
|
.use(remarkDirective)
|
|
164
176
|
.use(remarkGfm)
|
|
165
177
|
.use(tagDirectiveNodes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markii/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Framework-agnostic reference parser for Mark (.mk.md): CommonMark + directives to mdast, and mdast to a sanitized hast tree. Zero React dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"@types/mdast": "^4.0.4",
|
|
56
56
|
"mdast-util-directive": "^3.1.0",
|
|
57
57
|
"remark-directive": "^4.0.0",
|
|
58
|
+
"remark-frontmatter": "^5.0.0",
|
|
58
59
|
"remark-gfm": "^4.0.1",
|
|
59
60
|
"remark-parse": "^11.0.0",
|
|
60
61
|
"remark-rehype": "^11.1.2",
|