@geml/geml 1.5.1 → 1.7.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/LICENSE +21 -21
- package/README.md +248 -217
- package/codemap/adapters/crg.mjs +120 -120
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -658
- package/codemap/browser-stub.mjs +34 -29
- package/codemap/build.mjs +609 -609
- package/codemap/cross-stack.mjs +303 -303
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +480 -480
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +49 -49
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +431 -431
- package/codemap/normalize.mjs +275 -275
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +310 -310
- package/codemap/render-all.mjs +77 -77
- package/codemap/serve.mjs +585 -585
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +155 -148
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +4 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +16 -0
- package/dist/geml.d.ts +5 -1
- package/dist/geml.js +1277 -283
- package/dist/history.d.ts +11 -8
- package/dist/history.js +20 -15
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +108 -38
- package/dist/render-html.d.ts +5 -0
- package/dist/render-html.js +45 -36
- package/dist/render.d.ts +1 -0
- package/dist/render.js +172 -136
- package/dist/selector.d.ts +55 -0
- package/dist/selector.js +112 -0
- package/dist/serialize.js +12 -0
- package/dist/table.js +27 -1
- package/dist/to-md.js +5 -0
- package/package.json +67 -66
- package/skill/SKILL.md +82 -0
- package/skill/references/authoring.geml +333 -0
package/dist/render.js
CHANGED
|
@@ -538,6 +538,42 @@ export class RenderCtx {
|
|
|
538
538
|
const classes = classAttr(["text", ...b.classes]);
|
|
539
539
|
return `<div class="${classes}"${idAttr}>\n${inner}\n</div>`;
|
|
540
540
|
}
|
|
541
|
+
case "data": {
|
|
542
|
+
// GEP-0005: the page shows a PREVIEW under the same row discipline
|
|
543
|
+
// tables use (opts.tableRows, default 500) — the MODEL always keeps
|
|
544
|
+
// everything. Direction follows the format's reading order: jsonl is
|
|
545
|
+
// an append-log, so the newest (last) lines are the preview; json is
|
|
546
|
+
// one value and reads from the top.
|
|
547
|
+
const limit = this.opts.tableRows ?? 500;
|
|
548
|
+
const src = typeof b.attrs["src"] === "string" ? b.attrs["src"] : undefined;
|
|
549
|
+
const fmt = typeof b.attrs["format"] === "string" ? b.attrs["format"]
|
|
550
|
+
: src !== undefined && /\.jsonl$/i.test(src) ? "jsonl" : "json";
|
|
551
|
+
let lines = b.raw ?? [];
|
|
552
|
+
if (lines.every((l) => l.trim() === "")) {
|
|
553
|
+
if (b.value !== undefined) {
|
|
554
|
+
// src= content loaded at build time: preview the canonical form.
|
|
555
|
+
lines = fmt === "jsonl" && Array.isArray(b.value)
|
|
556
|
+
? b.value.map((v) => JSON.stringify(v))
|
|
557
|
+
: JSON.stringify(b.value, null, 2).split("\n");
|
|
558
|
+
}
|
|
559
|
+
else if (src !== undefined) {
|
|
560
|
+
// §9.4: a render-time source (http, or no resolver at build).
|
|
561
|
+
const cap0 = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
|
|
562
|
+
return `<figure${idAttr}><p class="table-note">external data <code>${esc(src)}</code> — loaded at render time</p>${cap0}</figure>`;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const tail = fmt === "jsonl" && lines.length > limit;
|
|
566
|
+
const shown = tail ? lines.slice(-limit) : lines.slice(0, limit);
|
|
567
|
+
const omitted = lines.length - shown.length;
|
|
568
|
+
const note = omitted > 0
|
|
569
|
+
? `<p class="table-note">${tail
|
|
570
|
+
? `showing the last ${shown.length} of ${lines.length} lines — earlier lines are in the document source`
|
|
571
|
+
: `showing the first ${shown.length} of ${lines.length} lines — the complete data is in the document source`}</p>`
|
|
572
|
+
: "";
|
|
573
|
+
const cap = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
|
|
574
|
+
const pre = `<pre class="data-src" data-format="${escAttr(fmt)}">${esc(shown.join("\n"))}</pre>`;
|
|
575
|
+
return `<figure${idAttr}>${tail ? note + pre : pre + note}${cap}</figure>`;
|
|
576
|
+
}
|
|
541
577
|
case "table":
|
|
542
578
|
return b.table ? this.table(b.table, b.id, caption) : `<p class="render-error">table failed to parse</p>`;
|
|
543
579
|
case "diagram":
|
|
@@ -1229,143 +1265,143 @@ function trunc(s, n) {
|
|
|
1229
1265
|
// ---------------------------------------------------------------------------
|
|
1230
1266
|
// Page shell, inline CSS, inline interactivity JS
|
|
1231
1267
|
// ---------------------------------------------------------------------------
|
|
1232
|
-
export const CSS = `
|
|
1233
|
-
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1234
|
-
* { box-sizing: border-box; }
|
|
1235
|
-
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1236
|
-
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1237
|
-
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1238
|
-
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1239
|
-
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1240
|
-
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1241
|
-
p { margin:.7em 0; }
|
|
1242
|
-
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1243
|
-
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1244
|
-
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1245
|
-
pre code { background:none; padding:0; font-size:.85em; }
|
|
1246
|
-
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1247
|
-
pre.output code { color:inherit; }
|
|
1248
|
-
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1249
|
-
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1250
|
-
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1251
|
-
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1252
|
-
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1253
|
-
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1254
|
-
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1255
|
-
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1256
|
-
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1257
|
-
figure { margin:1.2em 0; }
|
|
1258
|
-
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1259
|
-
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1260
|
-
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1261
|
-
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1262
|
-
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1263
|
-
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1264
|
-
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1265
|
-
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1266
|
-
table.geml-table td.computed { color:#0a7c52; }
|
|
1267
|
-
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1268
|
-
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1269
|
-
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1270
|
-
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1271
|
-
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1272
|
-
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1273
|
-
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1274
|
-
.media { max-width:100%; border-radius:8px; }
|
|
1275
|
-
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1276
|
-
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1277
|
-
sup.fn a { font-size:.75em; }
|
|
1278
|
-
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1279
|
-
.geml-footer code { font-size:.95em; }
|
|
1280
|
-
.code-graph { margin:1.4em 0; }
|
|
1281
|
-
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1282
|
-
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
1283
|
-
.cg-svg { display:block; }
|
|
1284
|
-
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1285
|
-
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1286
|
-
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1287
|
-
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1288
|
-
.cg-search-row:hover { background:var(--bd); }
|
|
1289
|
-
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1290
|
-
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1291
|
-
.cg-search-grp:first-of-type { border-top:0; }
|
|
1292
|
-
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1293
|
-
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1294
|
-
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1295
|
-
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1296
|
-
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1297
|
-
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1298
|
-
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1299
|
-
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1300
|
-
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1301
|
-
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1302
|
-
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1303
|
-
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
1304
|
-
.cg-flash { color:#b42318; }
|
|
1305
|
-
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1306
|
-
.cg-upbtn { cursor:pointer; }
|
|
1307
|
-
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1308
|
-
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1309
|
-
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1310
|
-
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1311
|
-
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1312
|
-
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1313
|
-
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1314
|
-
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1315
|
-
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1316
|
-
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1317
|
-
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1318
|
-
.cg-n { cursor:pointer; }
|
|
1319
|
-
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1320
|
-
.cg-n.leaf { opacity:.45; }
|
|
1321
|
-
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1322
|
-
.cg-n.grp rect { stroke-width:1.8; }
|
|
1323
|
-
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1324
|
-
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1325
|
-
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1326
|
-
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1327
|
-
.cg-e.soft { opacity:.55; }
|
|
1328
|
-
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1329
|
-
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1330
|
-
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1331
|
-
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1268
|
+
export const CSS = `
|
|
1269
|
+
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1270
|
+
* { box-sizing: border-box; }
|
|
1271
|
+
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1272
|
+
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1273
|
+
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1274
|
+
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1275
|
+
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1276
|
+
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1277
|
+
p { margin:.7em 0; }
|
|
1278
|
+
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1279
|
+
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1280
|
+
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1281
|
+
pre code { background:none; padding:0; font-size:.85em; }
|
|
1282
|
+
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1283
|
+
pre.output code { color:inherit; }
|
|
1284
|
+
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1285
|
+
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1286
|
+
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1287
|
+
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1288
|
+
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1289
|
+
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1290
|
+
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1291
|
+
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1292
|
+
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1293
|
+
figure { margin:1.2em 0; }
|
|
1294
|
+
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1295
|
+
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1296
|
+
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1297
|
+
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1298
|
+
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1299
|
+
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1300
|
+
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1301
|
+
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1302
|
+
table.geml-table td.computed { color:#0a7c52; }
|
|
1303
|
+
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1304
|
+
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1305
|
+
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1306
|
+
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1307
|
+
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1308
|
+
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1309
|
+
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1310
|
+
.media { max-width:100%; border-radius:8px; }
|
|
1311
|
+
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1312
|
+
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1313
|
+
sup.fn a { font-size:.75em; }
|
|
1314
|
+
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1315
|
+
.geml-footer code { font-size:.95em; }
|
|
1316
|
+
.code-graph { margin:1.4em 0; }
|
|
1317
|
+
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1318
|
+
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
1319
|
+
.cg-svg { display:block; }
|
|
1320
|
+
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1321
|
+
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1322
|
+
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1323
|
+
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1324
|
+
.cg-search-row:hover { background:var(--bd); }
|
|
1325
|
+
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1326
|
+
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1327
|
+
.cg-search-grp:first-of-type { border-top:0; }
|
|
1328
|
+
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1329
|
+
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1330
|
+
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1331
|
+
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1332
|
+
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1333
|
+
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1334
|
+
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1335
|
+
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1336
|
+
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1337
|
+
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1338
|
+
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1339
|
+
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
1340
|
+
.cg-flash { color:#b42318; }
|
|
1341
|
+
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1342
|
+
.cg-upbtn { cursor:pointer; }
|
|
1343
|
+
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1344
|
+
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1345
|
+
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1346
|
+
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1347
|
+
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1348
|
+
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1349
|
+
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1350
|
+
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1351
|
+
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1352
|
+
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1353
|
+
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1354
|
+
.cg-n { cursor:pointer; }
|
|
1355
|
+
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1356
|
+
.cg-n.leaf { opacity:.45; }
|
|
1357
|
+
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1358
|
+
.cg-n.grp rect { stroke-width:1.8; }
|
|
1359
|
+
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1360
|
+
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1361
|
+
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1362
|
+
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1363
|
+
.cg-e.soft { opacity:.55; }
|
|
1364
|
+
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1365
|
+
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1366
|
+
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1367
|
+
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1332
1368
|
`;
|
|
1333
|
-
export const JS = `
|
|
1334
|
-
(function () {
|
|
1335
|
-
function cmp(a, b) {
|
|
1336
|
-
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1337
|
-
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1338
|
-
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1339
|
-
}
|
|
1340
|
-
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1341
|
-
var tbody = table.tBodies[0];
|
|
1342
|
-
if (!tbody) return;
|
|
1343
|
-
// Sort on header click.
|
|
1344
|
-
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1345
|
-
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1346
|
-
th.addEventListener("click", function () {
|
|
1347
|
-
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1348
|
-
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1349
|
-
th.classList.add(dir);
|
|
1350
|
-
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1351
|
-
rows.sort(function (r1, r2) {
|
|
1352
|
-
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1353
|
-
return dir === "asc" ? c : -c;
|
|
1354
|
-
});
|
|
1355
|
-
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1356
|
-
});
|
|
1357
|
-
});
|
|
1358
|
-
// Filter rows.
|
|
1359
|
-
var fig = table.closest(".table-figure");
|
|
1360
|
-
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1361
|
-
if (input) input.addEventListener("input", function () {
|
|
1362
|
-
var q = input.value.toLowerCase();
|
|
1363
|
-
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1364
|
-
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1365
|
-
});
|
|
1366
|
-
});
|
|
1367
|
-
});
|
|
1368
|
-
})();
|
|
1369
|
+
export const JS = `
|
|
1370
|
+
(function () {
|
|
1371
|
+
function cmp(a, b) {
|
|
1372
|
+
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1373
|
+
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1374
|
+
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1375
|
+
}
|
|
1376
|
+
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1377
|
+
var tbody = table.tBodies[0];
|
|
1378
|
+
if (!tbody) return;
|
|
1379
|
+
// Sort on header click.
|
|
1380
|
+
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1381
|
+
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1382
|
+
th.addEventListener("click", function () {
|
|
1383
|
+
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1384
|
+
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1385
|
+
th.classList.add(dir);
|
|
1386
|
+
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1387
|
+
rows.sort(function (r1, r2) {
|
|
1388
|
+
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1389
|
+
return dir === "asc" ? c : -c;
|
|
1390
|
+
});
|
|
1391
|
+
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1392
|
+
});
|
|
1393
|
+
});
|
|
1394
|
+
// Filter rows.
|
|
1395
|
+
var fig = table.closest(".table-figure");
|
|
1396
|
+
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1397
|
+
if (input) input.addEventListener("input", function () {
|
|
1398
|
+
var q = input.value.toLowerCase();
|
|
1399
|
+
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1400
|
+
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
});
|
|
1404
|
+
})();
|
|
1369
1405
|
`;
|
|
1370
1406
|
// geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
|
|
1371
1407
|
// clicking a node re-roots the view inside the embedded slice. Algorithm as
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface Span {
|
|
2
|
+
start: number;
|
|
3
|
+
end: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Unit {
|
|
6
|
+
span: Span;
|
|
7
|
+
kind: "block" | "heading" | "footnote";
|
|
8
|
+
type?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
level?: number;
|
|
11
|
+
text?: string;
|
|
12
|
+
}
|
|
13
|
+
export type Selector = {
|
|
14
|
+
form: "list";
|
|
15
|
+
} | {
|
|
16
|
+
form: "id";
|
|
17
|
+
raw: string;
|
|
18
|
+
} | {
|
|
19
|
+
form: "type";
|
|
20
|
+
type: string;
|
|
21
|
+
} | {
|
|
22
|
+
form: "content";
|
|
23
|
+
type?: string;
|
|
24
|
+
hex: string;
|
|
25
|
+
nth: number;
|
|
26
|
+
} | {
|
|
27
|
+
form: "attr";
|
|
28
|
+
type: string;
|
|
29
|
+
key: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function sha8(text: string): string;
|
|
32
|
+
export declare function parseSelector(raw: string | undefined, attrsIdOf: (braces: string) => string | undefined): Selector;
|
|
33
|
+
export interface Addressed {
|
|
34
|
+
unit: Unit;
|
|
35
|
+
hex: string;
|
|
36
|
+
nth: number;
|
|
37
|
+
}
|
|
38
|
+
export declare function addressUnits(units: Unit[], textOf: (u: Unit) => string): Addressed[];
|
|
39
|
+
export declare function shortestAddress(a: Addressed, all: Addressed[]): string;
|
|
40
|
+
export type ContentHit = {
|
|
41
|
+
ok: true;
|
|
42
|
+
unit: Unit;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
why: "no-match";
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
why: "wrong-type";
|
|
49
|
+
found: string;
|
|
50
|
+
};
|
|
51
|
+
export declare function matchContent(sel: Extract<Selector, {
|
|
52
|
+
form: "content";
|
|
53
|
+
}>, all: Addressed[]): ContentHit;
|
|
54
|
+
export declare function discoveryHint(where: string): string;
|
|
55
|
+
export declare function matchType(type: string, all: Addressed[]): Unit[];
|
package/dist/selector.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Block selectors — the one syntax `get`, `set` and `history get` all address
|
|
2
|
+
// blocks with (design: docs/design/specs/2026-08-04-geml-get-set-selector-design-change.md).
|
|
3
|
+
//
|
|
4
|
+
// §2's rule: a selector is a FILTER over blocks, `{…}` holds keys, and the same
|
|
5
|
+
// abbreviation rule applies twice — `#id` is `{#id}` short, `@<hex>` is
|
|
6
|
+
// `{@<hex>}` short. Both are keys; they differ only in selectivity.
|
|
7
|
+
//
|
|
8
|
+
// This module is PURE: it parses selector text and matches it against a unit
|
|
9
|
+
// index the caller supplies. It deliberately imports nothing from geml.ts —
|
|
10
|
+
// that module runs the CLI dispatch on import, so depending on it here would
|
|
11
|
+
// turn `import { parseSelector }` into "run the CLI". The scan that produces
|
|
12
|
+
// `Unit[]` therefore stays in geml.ts (one walk, several sinks) and the
|
|
13
|
+
// selector logic stays here, where it can be unit-tested on plain data.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
// The content address's hash. Same spelling as the `.gemlhistory` unit key
|
|
16
|
+
// (history.ts:112) because both answer the same question — how to address a
|
|
17
|
+
// unit that carries no id. The VALUES are deliberately not promised to match:
|
|
18
|
+
// history hashes a tile (trailing blank lines included), this hashes a block's
|
|
19
|
+
// span (§3.3). Do not port an address from one layer to the other.
|
|
20
|
+
export function sha8(text) {
|
|
21
|
+
return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex").slice(0, 8);
|
|
22
|
+
}
|
|
23
|
+
const FENCE_SEL = /^={3,}[ \t]*([A-Za-z][A-Za-z0-9_-]*)[ \t]*(@[0-9a-fA-F]{1,}(?:~\d+)?)?[ \t]*(\{.*\})?[ \t]*$/;
|
|
24
|
+
const BARE_AT = /^@([0-9a-fA-F]+)(?:~(\d+))?$/;
|
|
25
|
+
// Parse selector TEXT. Never touches a document: every form is decided by
|
|
26
|
+
// lexis alone, which is also what keeps the two selector namespaces on
|
|
27
|
+
// `history get <file> <rev> <selector>` from overlapping (history design §10.2).
|
|
28
|
+
// `attrsIdOf` lets the caller reuse its own `{…}` parser (parseAttrs) rather
|
|
29
|
+
// than this module growing a second one.
|
|
30
|
+
export function parseSelector(raw, attrsIdOf) {
|
|
31
|
+
if (raw === undefined || raw.trim() === "")
|
|
32
|
+
return { form: "list" };
|
|
33
|
+
const s = raw.trim();
|
|
34
|
+
const bare = BARE_AT.exec(s);
|
|
35
|
+
if (bare)
|
|
36
|
+
return { form: "content", hex: bare[1].toLowerCase(), nth: bare[2] ? Number(bare[2]) : 0 };
|
|
37
|
+
const fence = FENCE_SEL.exec(s);
|
|
38
|
+
if (fence) {
|
|
39
|
+
const type = fence[1];
|
|
40
|
+
const at = fence[2];
|
|
41
|
+
const braces = fence[3];
|
|
42
|
+
if (braces !== undefined) {
|
|
43
|
+
// `=== type {#id}` is the id key written out in full — redundant but
|
|
44
|
+
// legal (§2). Any OTHER key is the declared-not-implemented form.
|
|
45
|
+
const id = attrsIdOf(braces);
|
|
46
|
+
if (id !== undefined)
|
|
47
|
+
return { form: "id", raw: `#${id}` };
|
|
48
|
+
return { form: "attr", type, key: firstKey(braces) };
|
|
49
|
+
}
|
|
50
|
+
if (at !== undefined) {
|
|
51
|
+
const m = BARE_AT.exec(at);
|
|
52
|
+
return { form: "content", type, hex: m[1].toLowerCase(), nth: m[2] ? Number(m[2]) : 0 };
|
|
53
|
+
}
|
|
54
|
+
return { form: "type", type };
|
|
55
|
+
}
|
|
56
|
+
// Anything else is an id or a pasted heading line; the caller resolves it.
|
|
57
|
+
return { form: "id", raw: s };
|
|
58
|
+
}
|
|
59
|
+
// The first key inside `{…}`, for the §7 error message. Best-effort: it only
|
|
60
|
+
// has to name what the caller typed, and a class (`.warn`) is reported as
|
|
61
|
+
// written so the message does not claim a key that is not there.
|
|
62
|
+
function firstKey(braces) {
|
|
63
|
+
const inner = braces.replace(/^\{/, "").replace(/\}$/, "").trim();
|
|
64
|
+
const m = /^([.#]?[A-Za-z_][A-Za-z0-9_-]*)/.exec(inner);
|
|
65
|
+
return m ? m[1] : inner.split(/[\s=]/)[0] ?? "";
|
|
66
|
+
}
|
|
67
|
+
export function addressUnits(units, textOf) {
|
|
68
|
+
const seen = new Map();
|
|
69
|
+
return units.map((unit) => {
|
|
70
|
+
// LF-normalized so a CRLF checkout and an LF one address the same block.
|
|
71
|
+
const hex = sha8(textOf(unit).replace(/\r\n?/g, "\n"));
|
|
72
|
+
const nth = seen.get(hex) ?? 0;
|
|
73
|
+
seen.set(hex, nth + 1);
|
|
74
|
+
return { unit, hex, nth };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// §6.1 — the SHORTEST address that identifies this unit uniquely, which is what
|
|
78
|
+
// the listing prints. `#id` when it has one; else the bare type when the
|
|
79
|
+
// document holds exactly one block of it; else the content address. The three
|
|
80
|
+
// cases are one rule ("shortest unique"), not three rules.
|
|
81
|
+
export function shortestAddress(a, all) {
|
|
82
|
+
const u = a.unit;
|
|
83
|
+
if (u.id !== undefined)
|
|
84
|
+
return `#${u.id}`;
|
|
85
|
+
if (u.type === undefined)
|
|
86
|
+
return `@${a.hex}${a.nth ? `~${a.nth}` : ""}`;
|
|
87
|
+
const sameType = all.filter((x) => x.unit.type === u.type).length;
|
|
88
|
+
if (sameType === 1)
|
|
89
|
+
return `=== ${u.type}`;
|
|
90
|
+
return `=== ${u.type}@${a.hex}${a.nth ? `~${a.nth}` : ""}`;
|
|
91
|
+
}
|
|
92
|
+
export function matchContent(sel, all) {
|
|
93
|
+
const hit = all.find((a) => a.hex === sel.hex && a.nth === sel.nth);
|
|
94
|
+
if (!hit)
|
|
95
|
+
return { ok: false, why: "no-match" };
|
|
96
|
+
if (sel.type !== undefined && hit.unit.type !== sel.type) {
|
|
97
|
+
return { ok: false, why: "wrong-type", found: hit.unit.type ?? hit.unit.kind };
|
|
98
|
+
}
|
|
99
|
+
return { ok: true, unit: hit.unit };
|
|
100
|
+
}
|
|
101
|
+
// Where to send a caller whose selector found nothing: the listing IS the
|
|
102
|
+
// discovery command, and every address it prints pastes straight back (§6.2).
|
|
103
|
+
// One place, so `get`, `set` and `history get` all point at the same next step.
|
|
104
|
+
export function discoveryHint(where) {
|
|
105
|
+
return ` — run \`geml get ${where}\` to list every addressable block`;
|
|
106
|
+
}
|
|
107
|
+
// Match a type filter: every block of that type in document order. Blocks that
|
|
108
|
+
// carry an id are INCLUDED — the selector says nothing about ids, so filtering
|
|
109
|
+
// by whether one is present would be a rule nobody wrote down (§2).
|
|
110
|
+
export function matchType(type, all) {
|
|
111
|
+
return all.filter((a) => a.unit.type === type).map((a) => a.unit);
|
|
112
|
+
}
|
package/dist/serialize.js
CHANGED
|
@@ -174,6 +174,18 @@ function serTypedBlock(b) {
|
|
|
174
174
|
else if (b.mode === "data") {
|
|
175
175
|
body = Object.entries(b.data ?? {}).map(([k, v]) => `${k} = ${serDataValue(v)}`);
|
|
176
176
|
}
|
|
177
|
+
else if (b.type === "data" && b.value !== undefined && b.attrs["src"] === undefined) {
|
|
178
|
+
// src= content lives in the FILE: a value loaded from it must never be
|
|
179
|
+
// inlined into the document by a re-format — the body stays as authored
|
|
180
|
+
// (normally empty), or the source of truth would silently fork.
|
|
181
|
+
// GEP-0005 canonical form — legal because this is DATA, not verbatim text
|
|
182
|
+
// (code stays byte-preserved): json pretty-prints at two spaces, jsonl is
|
|
183
|
+
// one compact value per line. Engine-less bodies (yaml/toml/unknown)
|
|
184
|
+
// never reach here (no `value`) and fall through byte-preserved.
|
|
185
|
+
body = String(b.attrs["format"] ?? "json") === "jsonl" && Array.isArray(b.value)
|
|
186
|
+
? b.value.map((v) => JSON.stringify(v))
|
|
187
|
+
: JSON.stringify(b.value, null, 2).split("\n");
|
|
188
|
+
}
|
|
177
189
|
else {
|
|
178
190
|
body = b.raw ?? [];
|
|
179
191
|
}
|
package/dist/table.js
CHANGED
|
@@ -60,6 +60,27 @@ function parseDelimited(body, sep, header) {
|
|
|
60
60
|
const width = rows.reduce((m, r) => Math.max(m, r.length), 0);
|
|
61
61
|
return { columns: letters(width), align: [], header: false, cells: rows };
|
|
62
62
|
}
|
|
63
|
+
// The character a data body splits on: the format's natural delimiter unless
|
|
64
|
+
// `delim=` names another single one — `;` for a European CSV, `|` for a
|
|
65
|
+
// pipe-delimited export (§6). Measured in code points, so an astral character
|
|
66
|
+
// counts as one. §4 attribute values carry no escape syntax, so a tab
|
|
67
|
+
// delimiter is spelled `format=tsv`, never `delim="\t"`; a value that is not
|
|
68
|
+
// exactly one character is an error and the natural delimiter is used, which
|
|
69
|
+
// keeps the rest of the table readable.
|
|
70
|
+
function resolveDelim(fmt, raw, diagnostics) {
|
|
71
|
+
const natural = fmt === "tsv" ? "\t" : ",";
|
|
72
|
+
if (raw === undefined)
|
|
73
|
+
return natural;
|
|
74
|
+
const d = String(raw);
|
|
75
|
+
if ([...d].length === 1)
|
|
76
|
+
return d;
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
severity: "error",
|
|
79
|
+
code: "bad-table-delimiter",
|
|
80
|
+
message: `\`delim="${d}"\` must be exactly one character (for a tab use \`format=tsv\`); split on ${fmt === "tsv" ? "a tab" : "`,`"} instead`,
|
|
81
|
+
});
|
|
82
|
+
return natural;
|
|
83
|
+
}
|
|
63
84
|
function letters(n) {
|
|
64
85
|
const out = [];
|
|
65
86
|
for (let i = 0; i < n; i++)
|
|
@@ -271,11 +292,16 @@ export function parseTable(body, attrs, line, sink) {
|
|
|
271
292
|
if (fmt === "csv" || fmt === "tsv") {
|
|
272
293
|
const headerAttr = attrs["header"];
|
|
273
294
|
const header = headerAttr === undefined ? true : headerAttr === true || headerAttr === 1 || headerAttr === "1";
|
|
274
|
-
raw = parseDelimited(body, fmt
|
|
295
|
+
raw = parseDelimited(body, resolveDelim(fmt, attrs["delim"], diagnostics), header);
|
|
275
296
|
}
|
|
276
297
|
else {
|
|
277
298
|
if (fmt !== undefined)
|
|
278
299
|
diagnostics.push({ severity: "warning", code: "unknown-table-format", message: `unknown table format \`${fmt}\`; parsed as visual grid` });
|
|
300
|
+
// `delim` refines the data form; it does not select it. Silently dropping it
|
|
301
|
+
// would leave a `=== table {delim=";"}` parsed as a one-column visual grid
|
|
302
|
+
// with nothing to say why.
|
|
303
|
+
if (attrs["delim"] !== undefined)
|
|
304
|
+
diagnostics.push({ severity: "warning", code: "ignored-table-delimiter", message: "`delim` applies to a data body (`format=csv`/`tsv`); this body was parsed as a visual grid, so it is ignored" });
|
|
279
305
|
raw = parseVisual(body);
|
|
280
306
|
}
|
|
281
307
|
const columns = [...raw.columns];
|
package/dist/to-md.js
CHANGED
|
@@ -144,6 +144,11 @@ function typedToMd(b, notes) {
|
|
|
144
144
|
const raw = b.raw ?? [];
|
|
145
145
|
if (b.type === "code")
|
|
146
146
|
return fence(attr(b, "lang") ?? "", raw);
|
|
147
|
+
// GEP-0005: a data block projects as a fenced code block in its format —
|
|
148
|
+
// the nearest GFM shape (Markdown has no verified-data construct; that loss
|
|
149
|
+
// is the usual --to md lossiness, not a defect of the projection).
|
|
150
|
+
if (b.type === "data")
|
|
151
|
+
return fence(attr(b, "format") ?? "json", raw);
|
|
147
152
|
if (b.type === "math")
|
|
148
153
|
return ["$$", ...raw, "$$"].join("\n");
|
|
149
154
|
if (b.type === "table" && b.table)
|