@geml/geml 1.1.1 → 1.4.2
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/LICENSE +21 -21
- package/README.md +155 -79
- package/codemap/adapters/crg.mjs +120 -109
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -229
- package/codemap/browser-stub.mjs +29 -24
- package/codemap/build.mjs +609 -354
- package/codemap/cross-stack.mjs +303 -0
- package/codemap/detect.mjs +399 -185
- package/codemap/emit.mjs +480 -361
- package/codemap/entries.mjs +129 -0
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +63 -0
- package/codemap/foldings.mjs +110 -0
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +172 -143
- package/codemap/normalize.mjs +0 -0
- package/codemap/recipe-trust.mjs +103 -0
- package/codemap/refresh.mjs +310 -160
- package/codemap/render-all.mjs +64 -64
- package/codemap/serve.mjs +578 -378
- package/codemap/sfc-virtualize.mjs +367 -0
- package/codemap/verify.mjs +148 -126
- package/dist/block-edit.d.ts +1 -0
- package/dist/block-edit.js +112 -0
- package/dist/from-md.js +66 -7
- package/dist/geml.d.ts +2 -1
- package/dist/geml.js +1103 -233
- package/dist/history.js +102 -62
- package/dist/inline.d.ts +2 -1
- package/dist/inline.js +61 -8
- package/dist/render-html.d.ts +3 -0
- package/dist/render-html.js +95 -0
- package/dist/render.d.ts +33 -3
- package/dist/render.js +558 -283
- package/dist/serialize.js +22 -2
- package/dist/table.js +40 -5
- package/dist/to-md.js +4 -0
- package/package.json +62 -58
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Structural id-rewriting for `geml set`. `set #id` names the block to edit,
|
|
2
|
+
// so the content spliced in must ADOPT that id — whatever id it declared (or
|
|
3
|
+
// none). This module performs that rewrite parse-aware (per head form) rather
|
|
4
|
+
// than by blind byte replacement, touching ONLY the id: type, classes,
|
|
5
|
+
// attributes, body and the fence pairing all ride along unchanged.
|
|
6
|
+
//
|
|
7
|
+
// Deliberately self-contained — it imports only the shared attribute parser,
|
|
8
|
+
// never geml.ts: geml.ts's module body runs the CLI on import, so a back-import
|
|
9
|
+
// would fire the whole command line just by loading this helper.
|
|
10
|
+
import { parseAttrs } from "./attrs.js";
|
|
11
|
+
// The two head forms, spelled to MIRROR geml.ts's FENCE_OPEN / HEADING (same
|
|
12
|
+
// language) but with the id-bearing brace tail split out so the id can be
|
|
13
|
+
// rewritten while every other byte is copied verbatim:
|
|
14
|
+
// FENCE_HEAD g1 = `=== type` g2 = ws g3 = `{…}`? g4 = trailing ws
|
|
15
|
+
// HEAD_HEAD g1 = `## text` g2 = ws g3 = `{…}`? g4 = trailing ws
|
|
16
|
+
const FENCE_HEAD = /^(={3,}[ \t]+[A-Za-z][A-Za-z0-9_-]*)([ \t]*)(\{.*\})?([ \t]*)$/;
|
|
17
|
+
const HEAD_HEAD = /^(#{1,6}[ \t]+.*?)([ \t]*)(\{[^}]*\})?([ \t]*)$/;
|
|
18
|
+
// Split into physical lines while keeping each line's terminator, so join("")
|
|
19
|
+
// is byte-exact — the same boundaries geml.ts's splitLines() uses. A line ends
|
|
20
|
+
// at `\n`, `\r\n`, or a lone `\r`.
|
|
21
|
+
function splitLines(source) {
|
|
22
|
+
return source.split(/(?<=\n|\r(?!\n))/);
|
|
23
|
+
}
|
|
24
|
+
// Strip a single trailing terminator from one physical line.
|
|
25
|
+
function stripEnding(line) {
|
|
26
|
+
return line.replace(/(\r\n|\r|\n)$/, "");
|
|
27
|
+
}
|
|
28
|
+
// Rewrite the id inside a `{…}` attribute block to `#newId`, keeping the braces
|
|
29
|
+
// and every other class/attr byte. If no id is present, insert `#newId` as the
|
|
30
|
+
// first token. The id token sits at a token boundary (`{` or whitespace) and
|
|
31
|
+
// never inside a quoted value, so the anchored match can't disturb a value like
|
|
32
|
+
// `caption="#x"`.
|
|
33
|
+
function rewriteBraces(braces, newId) {
|
|
34
|
+
if (parseAttrs(braces).id !== undefined) {
|
|
35
|
+
return braces.replace(/([{\s])#[^\s}]+/, `$1#${newId}`);
|
|
36
|
+
}
|
|
37
|
+
const inner = braces.slice(1, -1).replace(/^[ \t]*/, "");
|
|
38
|
+
return `{#${newId}${inner.length ? " " + inner : ""}}`;
|
|
39
|
+
}
|
|
40
|
+
// Rewrite a HEAD line's id declaration to `#newId`. Handles both head forms and
|
|
41
|
+
// all id states: existing brace id, brace attrs without an id, and no braces at
|
|
42
|
+
// all (append `{#newId}`). A line that is neither form is returned unchanged.
|
|
43
|
+
function rewriteHead(head, newId) {
|
|
44
|
+
const rebuild = (m) => {
|
|
45
|
+
const lead = m[1], ws = m[2] ?? "", braces = m[3], trail = m[4] ?? "";
|
|
46
|
+
if (braces)
|
|
47
|
+
return lead + ws + rewriteBraces(braces, newId) + trail;
|
|
48
|
+
return `${lead} {#${newId}}${ws}${trail}`;
|
|
49
|
+
};
|
|
50
|
+
const f = FENCE_HEAD.exec(head);
|
|
51
|
+
if (f)
|
|
52
|
+
return rebuild(f);
|
|
53
|
+
const h = HEAD_HEAD.exec(head);
|
|
54
|
+
if (h)
|
|
55
|
+
return rebuild(h);
|
|
56
|
+
return head;
|
|
57
|
+
}
|
|
58
|
+
// Locate the block's HEAD: the first non-blank, non-`%%` line that opens a fence
|
|
59
|
+
// or a heading. Returns its line index, or -1 when the content has no head
|
|
60
|
+
// (pure prose, or a structural line that is not a head) — the caller decides
|
|
61
|
+
// what that means.
|
|
62
|
+
function findHead(lines) {
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
const t = stripEnding(lines[i]);
|
|
65
|
+
if (t.trim() === "" || /^[ \t]*%%/.test(t))
|
|
66
|
+
continue;
|
|
67
|
+
if (FENCE_HEAD.test(t) || HEAD_HEAD.test(t))
|
|
68
|
+
return i;
|
|
69
|
+
return -1; // the first structural line isn't a head: no addressable block
|
|
70
|
+
}
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
// Rewrite the HEAD id of the first block in `blockSrc` to `newId`, across every
|
|
74
|
+
// head form:
|
|
75
|
+
// • fence attrs `{#x …}` -> `{#newId …}` (other classes/attrs kept)
|
|
76
|
+
// • fence with attrs but no id, or no braces -> gains `{#newId}`
|
|
77
|
+
// • labeled close `=== #x` -> `=== #newId` (renamed to match the open)
|
|
78
|
+
// • heading `## T {#x}` -> `## T {#newId}`
|
|
79
|
+
// • heading auto-slug (no braces) -> `## T {#newId}` appended
|
|
80
|
+
// Only the id changes; type / classes / attrs / body / fence length are byte-
|
|
81
|
+
// preserved, as are line terminators. Content with no head is returned as-is.
|
|
82
|
+
export function normalizeBlockId(blockSrc, newId) {
|
|
83
|
+
const lines = splitLines(blockSrc);
|
|
84
|
+
const hi = findHead(lines);
|
|
85
|
+
if (hi < 0)
|
|
86
|
+
return blockSrc;
|
|
87
|
+
const headText = stripEnding(lines[hi]);
|
|
88
|
+
const headTerm = lines[hi].slice(headText.length);
|
|
89
|
+
lines[hi] = rewriteHead(headText, newId) + headTerm;
|
|
90
|
+
// For a fence carrying an id, a labeled close `=== #oldId` names that id and
|
|
91
|
+
// must be renamed too — otherwise the open declares #newId while the close
|
|
92
|
+
// still labels #oldId and the block no longer parses. The FIRST close wins
|
|
93
|
+
// (plain equal-length OR labeled), matching geml.ts's fenceClose scan; a
|
|
94
|
+
// plain close needs no rewrite.
|
|
95
|
+
const f = FENCE_HEAD.exec(headText);
|
|
96
|
+
const oldId = f && f[3] ? parseAttrs(f[3]).id : undefined;
|
|
97
|
+
if (f && oldId !== undefined) {
|
|
98
|
+
const openLen = /^=+/.exec(f[1])[0].length;
|
|
99
|
+
for (let j = hi + 1; j < lines.length; j++) {
|
|
100
|
+
const ct = stripEnding(lines[j]);
|
|
101
|
+
const trimmed = ct.replace(/[ \t]+$/, "");
|
|
102
|
+
if (/^=+$/.test(trimmed) && trimmed.length === openLen)
|
|
103
|
+
break; // plain close: done
|
|
104
|
+
const cm = /^(={3,}[ \t]+#)([^\s}]+)([ \t]*)$/.exec(ct);
|
|
105
|
+
if (cm && cm[2] === oldId) {
|
|
106
|
+
lines[j] = cm[1] + newId + cm[3] + lines[j].slice(ct.length);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return lines.join("");
|
|
112
|
+
}
|
package/dist/from-md.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// thematic break (---/***) -> dropped (§1: not part of GEML)
|
|
15
15
|
//
|
|
16
16
|
// Anything else (ATX headings, lists, paragraphs) is already valid GEML.
|
|
17
|
+
import { META_REF_SRC } from "./inline.js";
|
|
17
18
|
// Pick a fence length longer than any run of `=` that appears alone on a body
|
|
18
19
|
// line (leading indentation included), so the close fence stays unambiguous
|
|
19
20
|
// (§3 equal-length close rule) and the body's own `=` fences nest safely.
|
|
@@ -68,6 +69,63 @@ function autolinks(s) {
|
|
|
68
69
|
.replace(/<mailto:([^>\s]+)>/g, "[$1](mailto:$1)"))
|
|
69
70
|
.join("");
|
|
70
71
|
}
|
|
72
|
+
// A literal `{{name}}` in Markdown prose is plain text (Markdown has no
|
|
73
|
+
// metadata interpolation), but converted GEML flow text would read it as a §4
|
|
74
|
+
// reference — an unknown key fails `geml check`, and a key that happens to
|
|
75
|
+
// exist in the generated `=== meta` block substitutes silently. Escape it to
|
|
76
|
+
// `\{{name}}`, skipping the spans GEML interpolation itself leaves verbatim
|
|
77
|
+
// (inline code, inline math) and already-escaped characters.
|
|
78
|
+
const META_REF_Y = new RegExp(META_REF_SRC, "y");
|
|
79
|
+
function escMetaRefs(s) {
|
|
80
|
+
if (!s.includes("{{"))
|
|
81
|
+
return s;
|
|
82
|
+
let out = "";
|
|
83
|
+
let i = 0;
|
|
84
|
+
while (i < s.length) {
|
|
85
|
+
const c = s[i];
|
|
86
|
+
if (c === "\\" && i + 1 < s.length) {
|
|
87
|
+
out += s.slice(i, i + 2);
|
|
88
|
+
i += 2;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (c === "`") {
|
|
92
|
+
let n = 0;
|
|
93
|
+
while (s[i + n] === "`")
|
|
94
|
+
n++;
|
|
95
|
+
const close = s.indexOf("`".repeat(n), i + n);
|
|
96
|
+
if (close >= 0) {
|
|
97
|
+
out += s.slice(i, close + n);
|
|
98
|
+
i = close + n;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
out += s.slice(i, i + n);
|
|
102
|
+
i += n;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (c === "$") {
|
|
106
|
+
const close = s.indexOf("$", i + 1);
|
|
107
|
+
if (close > i + 1) {
|
|
108
|
+
out += s.slice(i, close + 1);
|
|
109
|
+
i = close + 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
out += c;
|
|
113
|
+
i++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (c === "{" && s[i + 1] === "{") {
|
|
117
|
+
META_REF_Y.lastIndex = i;
|
|
118
|
+
if (META_REF_Y.test(s)) {
|
|
119
|
+
out += "\\{";
|
|
120
|
+
i++;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
out += c;
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
71
129
|
// GitHub-style heading anchor: drop code backticks (keep content), lowercase,
|
|
72
130
|
// strip punctuation except `-`/`_`, collapse whitespace to hyphens. Used to keep
|
|
73
131
|
// converted headings' ids in sync with Markdown TOC links.
|
|
@@ -151,12 +209,12 @@ export function mdToGeml(source) {
|
|
|
151
209
|
if (line.trim() !== "" && !THEMATIC.test(line) && i + 1 < lines.length) {
|
|
152
210
|
const nxt = lines[i + 1];
|
|
153
211
|
if (SETEXT_UL.test(nxt)) {
|
|
154
|
-
out.push(`# ${line.trim()}`);
|
|
212
|
+
out.push(`# ${escMetaRefs(line.trim())}`);
|
|
155
213
|
i += 2;
|
|
156
214
|
continue;
|
|
157
215
|
}
|
|
158
216
|
if (SETEXT_DASH.test(nxt)) {
|
|
159
|
-
out.push(`## ${line.trim()}`);
|
|
217
|
+
out.push(`## ${escMetaRefs(line.trim())}`);
|
|
160
218
|
i += 2;
|
|
161
219
|
continue;
|
|
162
220
|
}
|
|
@@ -183,7 +241,7 @@ export function mdToGeml(source) {
|
|
|
183
241
|
else
|
|
184
242
|
break;
|
|
185
243
|
}
|
|
186
|
-
emitBlock(out, "note", `{#${fn[1].trim()}}`, body.map(autolinks), ids);
|
|
244
|
+
emitBlock(out, "note", `{#${fn[1].trim()}}`, body.map((l) => escMetaRefs(autolinks(l))), ids);
|
|
187
245
|
i = j;
|
|
188
246
|
continue;
|
|
189
247
|
}
|
|
@@ -191,7 +249,7 @@ export function mdToGeml(source) {
|
|
|
191
249
|
if (/^\s*>/.test(line)) {
|
|
192
250
|
const body = [];
|
|
193
251
|
while (i < lines.length && /^\s*>/.test(lines[i])) {
|
|
194
|
-
body.push(lines[i].replace(/^\s*>\s?/, ""));
|
|
252
|
+
body.push(escMetaRefs(lines[i].replace(/^\s*>\s?/, "")));
|
|
195
253
|
i++;
|
|
196
254
|
}
|
|
197
255
|
emitBlock(out, "note", "", body, ids);
|
|
@@ -220,13 +278,14 @@ export function mdToGeml(source) {
|
|
|
220
278
|
if (atx && atx[2].includes("`") && !/\{[^}]*\}\s*$/.test(atx[2])) {
|
|
221
279
|
const id = githubSlug(atx[2]);
|
|
222
280
|
if (id) {
|
|
223
|
-
out.push(`${atx[1]} ${atx[2]} {#${id}}`);
|
|
281
|
+
out.push(`${atx[1]} ${escMetaRefs(atx[2])} {#${id}}`);
|
|
224
282
|
i++;
|
|
225
283
|
continue;
|
|
226
284
|
}
|
|
227
285
|
}
|
|
228
|
-
// Inline pass: rewrite autolinks to GEML links
|
|
229
|
-
|
|
286
|
+
// Inline pass: rewrite autolinks to GEML links, escape literal `{{name}}`
|
|
287
|
+
// (both outside code spans).
|
|
288
|
+
const text = escMetaRefs(autolinks(line));
|
|
230
289
|
// Raw HTML note — ignore `<…>` that sits inside an inline code span.
|
|
231
290
|
if (/<[a-zA-Z/]/.test(text.replace(/`[^`]*`/g, ""))) {
|
|
232
291
|
notes.push(`raw HTML kept as text at line ${i + 1}: ${line.trim().slice(0, 40)}`);
|
package/dist/geml.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export { type Value } from "./attrs.js";
|
|
|
7
7
|
export { type Inline } from "./inline.js";
|
|
8
8
|
export { type TableModel } from "./table.js";
|
|
9
9
|
export { mdToGeml, type ConvertResult } from "./from-md.js";
|
|
10
|
-
export { renderHtml
|
|
10
|
+
export { renderHtml } from "./render-html.js";
|
|
11
|
+
export { type RenderOptions } from "./render.js";
|
|
11
12
|
export { serialize } from "./serialize.js";
|
|
12
13
|
export { gemlToMd } from "./to-md.js";
|
|
13
14
|
export type BodyMode = "raw" | "flow" | "data";
|