nebula-notebook 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +124 -24
  2. package/dist/assets/errorwidget-C4r2j2DQ.js +5 -0
  3. package/dist/assets/fa-brands-400-CEJbCg16.woff +0 -0
  4. package/dist/assets/fa-brands-400-CSYNqBb_.ttf +0 -0
  5. package/dist/assets/fa-brands-400-DnkPfk3o.eot +0 -0
  6. package/dist/assets/fa-brands-400-UxlILjvJ.woff2 +0 -0
  7. package/dist/assets/fa-brands-400-cH1MgKbP.svg +3717 -0
  8. package/dist/assets/fa-regular-400-BhTwtT8w.eot +0 -0
  9. package/dist/assets/fa-regular-400-D1vz6WBx.ttf +0 -0
  10. package/dist/assets/fa-regular-400-DFnMcJPd.woff +0 -0
  11. package/dist/assets/fa-regular-400-DGzu1beS.woff2 +0 -0
  12. package/dist/assets/fa-regular-400-gwj8Pxq-.svg +801 -0
  13. package/dist/assets/fa-solid-900-B4ZZ7kfP.svg +5034 -0
  14. package/dist/assets/fa-solid-900-B6Axprfb.eot +0 -0
  15. package/dist/assets/fa-solid-900-BUswJgRo.woff2 +0 -0
  16. package/dist/assets/fa-solid-900-DOXgCApm.woff +0 -0
  17. package/dist/assets/fa-solid-900-mxuxnBEa.ttf +0 -0
  18. package/dist/assets/index-7-YBurka.js +716 -0
  19. package/dist/assets/index-BtWv4MIT.css +7 -0
  20. package/dist/assets/index-CFBUnxSZ.css +32 -0
  21. package/dist/assets/index-CsHoPQy-.js +1 -0
  22. package/dist/assets/index-D5w21_Z8.js +81 -0
  23. package/dist/assets/index-Day3QcNs.js +1 -0
  24. package/dist/assets/services-shim-D6p_A67v.js +33 -0
  25. package/dist/assets/viewlist-uomDf7I7.js +1 -0
  26. package/dist/assets/widgets-X7J3NxEn.css +1 -0
  27. package/dist/index.html +2 -2
  28. package/node-server/dist/cluster/client-registration.js +3 -0
  29. package/node-server/dist/cluster/kernel-proxy.js +24 -9
  30. package/node-server/dist/cluster/server-registry.d.ts +8 -0
  31. package/node-server/dist/cluster/server-registry.js +31 -7
  32. package/node-server/dist/fs/fs-service.d.ts +55 -7
  33. package/node-server/dist/fs/fs-service.js +489 -80
  34. package/node-server/dist/fs/notebook-formats/percent.d.ts +25 -0
  35. package/node-server/dist/fs/notebook-formats/percent.js +286 -0
  36. package/node-server/dist/fs/notebook-formats/qmd.d.ts +29 -0
  37. package/node-server/dist/fs/notebook-formats/qmd.js +307 -0
  38. package/node-server/dist/fs/notebook-formats/registry.d.ts +12 -0
  39. package/node-server/dist/fs/notebook-formats/registry.js +77 -0
  40. package/node-server/dist/fs/notebook-formats/types.d.ts +37 -0
  41. package/node-server/dist/fs/notebook-formats/types.js +13 -0
  42. package/node-server/dist/idle-exit.d.ts +52 -0
  43. package/node-server/dist/idle-exit.js +83 -0
  44. package/node-server/dist/index.js +129 -9
  45. package/node-server/dist/kernel/kernel-service.d.ts +113 -2
  46. package/node-server/dist/kernel/kernel-service.js +762 -60
  47. package/node-server/dist/notebook/cell-hash.d.ts +13 -0
  48. package/node-server/dist/notebook/cell-hash.js +26 -0
  49. package/node-server/dist/notebook/headless-handler.d.ts +9 -0
  50. package/node-server/dist/notebook/headless-handler.js +124 -23
  51. package/node-server/dist/notebook/operation-router.d.ts +36 -0
  52. package/node-server/dist/notebook/operation-router.js +224 -9
  53. package/node-server/dist/notebook/undoRedoManager.d.ts +4 -1
  54. package/node-server/dist/notebook/undoRedoManager.js +10 -2
  55. package/node-server/dist/output/display-data.js +2 -0
  56. package/node-server/dist/routes/cluster.js +2 -2
  57. package/node-server/dist/routes/compute.d.ts +8 -0
  58. package/node-server/dist/routes/compute.js +136 -0
  59. package/node-server/dist/routes/fs.js +2 -2
  60. package/node-server/dist/routes/kernel.js +115 -3
  61. package/node-server/dist/routes/notebook.js +35 -1
  62. package/node-server/dist/scheduler/allocation-service.d.ts +43 -0
  63. package/node-server/dist/scheduler/allocation-service.js +169 -0
  64. package/node-server/dist/scheduler/job-template.d.ts +30 -0
  65. package/node-server/dist/scheduler/job-template.js +85 -0
  66. package/node-server/dist/scheduler/mock-scheduler.d.ts +30 -0
  67. package/node-server/dist/scheduler/mock-scheduler.js +121 -0
  68. package/node-server/dist/scheduler/slurm-scheduler.d.ts +31 -0
  69. package/node-server/dist/scheduler/slurm-scheduler.js +393 -0
  70. package/node-server/dist/scheduler/types.d.ts +117 -0
  71. package/node-server/dist/scheduler/types.js +8 -0
  72. package/node-server/dist/scheduler/util.d.ts +7 -0
  73. package/node-server/dist/scheduler/util.js +20 -0
  74. package/node-server/dist/terminal/pty-manager.js +8 -0
  75. package/node-server/dist/terminal/server.js +43 -2
  76. package/node-server/dist/update-check.d.ts +20 -0
  77. package/node-server/dist/update-check.js +114 -0
  78. package/node-server/package.json +1 -3
  79. package/package.json +3 -5
  80. package/dist/assets/index-C1h_sArD.css +0 -32
  81. package/dist/assets/index-CDSTBon8.js +0 -658
@@ -0,0 +1,25 @@
1
+ /**
2
+ * jupytext percent-format adapter for #-comment languages (.py, .R, .jl).
3
+ *
4
+ * The format is defined entirely in terms of `#` line comments (markers,
5
+ * header, commented markdown bodies), so the same parser/serializer works
6
+ * verbatim for any language that uses `#` comments — Python, R, Julia.
7
+ * The file extension only decides the default kernel (see registry).
8
+ *
9
+ * Format essentials:
10
+ * - optional jupytext-style header: `# ---` / `# `-prefixed YAML / `# ---`
11
+ * - `# %%` markers start cells; `[markdown]` / `[raw]` tag cell type;
12
+ * `id=<token>` carries the Nebula cell id (jupytext-legal key=value)
13
+ * - markdown/raw bodies are `# `-commented
14
+ * - outputs and execution counts are never serialized
15
+ *
16
+ * Anything on a marker line that we don't understand is preserved verbatim
17
+ * (cell._metadata.percent_header_rest) and re-emitted — never interpreted.
18
+ */
19
+ import { NotebookFormatAdapter } from './types';
20
+ /** Content-based discrimination (the VS Code/jupytext convention): a .py
21
+ * file IS a percent notebook iff it has a `# %%` cell marker or a jupytext
22
+ * header. Marker-less files are plain scripts — Nebula must never write
23
+ * metadata into them as a side effect of merely opening them. */
24
+ export declare function isPercentNotebookText(text: string): boolean;
25
+ export declare const percentAdapter: NotebookFormatAdapter;
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * jupytext percent-format adapter for #-comment languages (.py, .R, .jl).
4
+ *
5
+ * The format is defined entirely in terms of `#` line comments (markers,
6
+ * header, commented markdown bodies), so the same parser/serializer works
7
+ * verbatim for any language that uses `#` comments — Python, R, Julia.
8
+ * The file extension only decides the default kernel (see registry).
9
+ *
10
+ * Format essentials:
11
+ * - optional jupytext-style header: `# ---` / `# `-prefixed YAML / `# ---`
12
+ * - `# %%` markers start cells; `[markdown]` / `[raw]` tag cell type;
13
+ * `id=<token>` carries the Nebula cell id (jupytext-legal key=value)
14
+ * - markdown/raw bodies are `# `-commented
15
+ * - outputs and execution counts are never serialized
16
+ *
17
+ * Anything on a marker line that we don't understand is preserved verbatim
18
+ * (cell._metadata.percent_header_rest) and re-emitted — never interpreted.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.percentAdapter = void 0;
55
+ exports.isPercentNotebookText = isPercentNotebookText;
56
+ const YAML = __importStar(require("yaml"));
57
+ const MARKER_RE = /^#\s*%%(.*)$/;
58
+ // Accept quoted (proper jupytext key="value" metadata) and bare id tokens
59
+ const ID_TOKEN_RE = /(^|\s)id="?([A-Za-z0-9_-]+)"?/;
60
+ function normalizeEol(text) {
61
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
62
+ }
63
+ function parseMarkerRemainder(remainder) {
64
+ let rest = remainder;
65
+ let cellType = 'code';
66
+ if (/(^|\s)\[markdown\](\s|$)/.test(rest)) {
67
+ cellType = 'markdown';
68
+ rest = rest.replace(/(^|\s)\[markdown\](?=\s|$)/, '$1');
69
+ }
70
+ else if (/(^|\s)\[raw\](\s|$)/.test(rest)) {
71
+ cellType = 'raw';
72
+ rest = rest.replace(/(^|\s)\[raw\](?=\s|$)/, '$1');
73
+ }
74
+ let id = null;
75
+ const idMatch = rest.match(ID_TOKEN_RE);
76
+ if (idMatch) {
77
+ id = idMatch[2];
78
+ rest = rest.replace(ID_TOKEN_RE, '$1');
79
+ }
80
+ return { cellType, id, rest: rest.replace(/\s+/g, ' ').trim() };
81
+ }
82
+ /** Markdown/raw bodies are `# `-commented. A body line that would itself
83
+ * render as a cell marker is backslash-escaped (`\%%` ↔ `%%`) so the
84
+ * file-level scanner can never mistake it for a marker. Classical escape:
85
+ * serializer prepends one backslash to any leading `\*%%` run, parser
86
+ * removes one. Lossless for all inputs. */
87
+ function encodeCommentLine(line) {
88
+ const escaped = /^(\s*)(\\*)%%/.test(line)
89
+ ? line.replace(/^(\s*)(\\*)%%/, '$1\\$2%%')
90
+ : line;
91
+ return escaped === '' ? '#' : `# ${escaped}`;
92
+ }
93
+ function decodeCommentLine(line) {
94
+ let body;
95
+ if (line === '#')
96
+ body = '';
97
+ else if (line.startsWith('# '))
98
+ body = line.slice(2);
99
+ else if (line.startsWith('#'))
100
+ body = line.slice(1);
101
+ else
102
+ body = line; // lenient: uncommented line inside a markdown body
103
+ return /^(\s*)\\(\\*)%%/.test(body) ? body.replace(/^(\s*)\\(\\*)%%/, '$1$2%%') : body;
104
+ }
105
+ function parseHeader(lines) {
106
+ if (lines[0] !== '# ---')
107
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
108
+ let end = -1;
109
+ for (let i = 1; i < lines.length; i++) {
110
+ if (lines[i] === '# ---') {
111
+ end = i;
112
+ break;
113
+ }
114
+ if (!lines[i].startsWith('#'))
115
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
116
+ }
117
+ if (end === -1)
118
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
119
+ const yamlText = lines
120
+ .slice(1, end)
121
+ .map((l) => (l === '#' ? '' : l.startsWith('# ') ? l.slice(2) : l.slice(1)))
122
+ .join('\n');
123
+ let headerObj;
124
+ try {
125
+ const parsed = YAML.parse(yamlText);
126
+ headerObj = parsed && typeof parsed === 'object' ? parsed : {};
127
+ }
128
+ catch {
129
+ // Unparseable header: treat the whole block as content rather than lose it
130
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
131
+ }
132
+ const jupyter = (headerObj.jupyter && typeof headerObj.jupyter === 'object'
133
+ ? headerObj.jupyter
134
+ : {});
135
+ const metadata = { ...jupyter };
136
+ const extra = {};
137
+ for (const [k, v] of Object.entries(headerObj)) {
138
+ if (k !== 'jupyter')
139
+ extra[k] = v;
140
+ }
141
+ if (Object.keys(extra).length > 0)
142
+ metadata.__percent_header_extra = extra;
143
+ const kernelspec = metadata.kernelspec;
144
+ const kernelspecName = typeof kernelspec?.name === 'string' ? kernelspec.name : null;
145
+ // Skip one blank separator line after the header if present
146
+ let bodyStart = end + 1;
147
+ if (lines[bodyStart] === '')
148
+ bodyStart++;
149
+ return { metadata, kernelspecName, bodyStart };
150
+ }
151
+ function makeCell(index, info, bodyLines, trimSeparator) {
152
+ const cellType = info?.cellType ?? 'code';
153
+ // The serializer emits exactly one blank separator line between a cell body
154
+ // and the NEXT marker. Trim it only when a following marker caused this
155
+ // flush — at EOF there is no separator, so trailing blanks belong to the
156
+ // cell content itself.
157
+ const body = [...bodyLines];
158
+ if (trimSeparator && body[body.length - 1] === '')
159
+ body.pop();
160
+ const content = cellType === 'code'
161
+ ? body.join('\n')
162
+ : body.map(decodeCommentLine).join('\n');
163
+ const cell = {
164
+ id: info?.id ?? `cell-${index}`,
165
+ type: cellType === 'code' ? 'code' : 'markdown',
166
+ content,
167
+ outputs: [],
168
+ executionCount: null,
169
+ isExecuting: false,
170
+ };
171
+ const meta = {};
172
+ if (cellType === 'raw')
173
+ meta.percent_cell_type = 'raw';
174
+ if (info && info.rest)
175
+ meta.percent_header_rest = info.rest;
176
+ if (Object.keys(meta).length > 0)
177
+ cell._metadata = meta;
178
+ return cell;
179
+ }
180
+ function parse(text) {
181
+ const lines = normalizeEol(text).split('\n');
182
+ // Drop the implicit final empty string from a trailing newline
183
+ if (lines.length > 0 && lines[lines.length - 1] === '')
184
+ lines.pop();
185
+ const { metadata, kernelspecName, bodyStart } = parseHeader(lines);
186
+ const cells = [];
187
+ let currentInfo = null;
188
+ let currentBody = [];
189
+ let sawMarker = false;
190
+ let started = false; // whether any non-blank content seen before first marker
191
+ const flush = (trimSeparator) => {
192
+ if (currentInfo === null && !started)
193
+ return; // nothing accumulated
194
+ cells.push(makeCell(cells.length, currentInfo, currentBody, trimSeparator));
195
+ };
196
+ for (let i = bodyStart; i < lines.length; i++) {
197
+ const line = lines[i];
198
+ const m = line.match(MARKER_RE);
199
+ if (m) {
200
+ if (sawMarker || started)
201
+ flush(true);
202
+ currentInfo = parseMarkerRemainder(m[1]);
203
+ currentBody = [];
204
+ sawMarker = true;
205
+ started = true;
206
+ }
207
+ else {
208
+ if (!started && line.trim() !== '')
209
+ started = true;
210
+ currentBody.push(line);
211
+ }
212
+ }
213
+ if (sawMarker || started)
214
+ flush(false);
215
+ return { cells, metadata, kernelspecName };
216
+ }
217
+ function serializeHeader(metadata, kernelName) {
218
+ const jupyter = {};
219
+ for (const [k, v] of Object.entries(metadata)) {
220
+ if (k !== '__percent_header_extra')
221
+ jupyter[k] = v;
222
+ }
223
+ if (kernelName) {
224
+ const existing = (jupyter.kernelspec && typeof jupyter.kernelspec === 'object'
225
+ ? jupyter.kernelspec
226
+ : {});
227
+ jupyter.kernelspec = { ...existing, name: kernelName };
228
+ }
229
+ const extra = (metadata.__percent_header_extra ?? {});
230
+ const headerObj = { ...extra };
231
+ if (Object.keys(jupyter).length > 0)
232
+ headerObj.jupyter = jupyter;
233
+ if (Object.keys(headerObj).length === 0)
234
+ return [];
235
+ const yamlText = YAML.stringify(headerObj, { lineWidth: 0 }).trimEnd();
236
+ return [
237
+ '# ---',
238
+ ...yamlText.split('\n').map((l) => (l === '' ? '#' : `# ${l}`)),
239
+ '# ---',
240
+ ];
241
+ }
242
+ function serializeMarker(cell) {
243
+ const meta = cell._metadata ?? {};
244
+ const parts = [];
245
+ const rest = typeof meta.percent_header_rest === 'string' ? meta.percent_header_rest : '';
246
+ if (rest)
247
+ parts.push(rest);
248
+ if (meta.percent_cell_type === 'raw')
249
+ parts.push('[raw]');
250
+ else if (cell.type === 'markdown')
251
+ parts.push('[markdown]');
252
+ parts.push(`id="${cell.id}"`);
253
+ return `# %% ${parts.join(' ')}`;
254
+ }
255
+ function serialize(cells, metadata, kernelName) {
256
+ const out = serializeHeader(metadata, kernelName);
257
+ cells.forEach((cell, i) => {
258
+ // Exactly one blank separator before every marker except a file-leading one
259
+ if (out.length > 0)
260
+ out.push('');
261
+ out.push(serializeMarker(cell));
262
+ const contentLines = cell.content === '' ? [] : cell.content.split('\n');
263
+ if (cell.type === 'code')
264
+ out.push(...contentLines);
265
+ else
266
+ out.push(...contentLines.map(encodeCommentLine));
267
+ void i;
268
+ });
269
+ return out.join('\n') + '\n';
270
+ }
271
+ /** Content-based discrimination (the VS Code/jupytext convention): a .py
272
+ * file IS a percent notebook iff it has a `# %%` cell marker or a jupytext
273
+ * header. Marker-less files are plain scripts — Nebula must never write
274
+ * metadata into them as a side effect of merely opening them. */
275
+ function isPercentNotebookText(text) {
276
+ if (/^#\s*%%/m.test(text))
277
+ return true;
278
+ return text.startsWith('# ---\n');
279
+ }
280
+ exports.percentAdapter = {
281
+ name: 'percent',
282
+ extensions: ['.py', '.r', '.jl'],
283
+ capabilities: { storesOutputs: false, storesCellIds: true },
284
+ parse,
285
+ serialize,
286
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Quarto adapter (.qmd) — notebook-first.
3
+ *
4
+ * A .qmd is treated as a notebook that doesn't serialize outputs:
5
+ * - optional YAML front matter (kept opaque; jupyter/nebula keys normalized)
6
+ * - ```{lang ...} fences are code cells; `#| id: x` carries the cell id;
7
+ * all other `#|` chunk options stay in cell content verbatim (they are
8
+ * valid comments) and are never interpreted
9
+ * - prose between fences becomes markdown cells
10
+ *
11
+ * No Quarto rendering, no chunk-option semantics, one kernel per document.
12
+ *
13
+ * Markdown cells are delimited by an HTML-comment marker mirroring the
14
+ * chunk-option syntax: `<!-- #| id: abc -->`. HTML comments are invisible in
15
+ * rendered Quarto output, so this is notebook-first without rendering cost:
16
+ * adjacent markdown cells stay distinct, empty markdown cells survive, and
17
+ * markdown ids persist (no positional fallback for Nebula-written files).
18
+ * Foreign .qmd files without markers still parse (one implicit markdown cell
19
+ * per prose block, positional ids); markers appear on first Nebula save.
20
+ *
21
+ * Known unrepresentables (inherent):
22
+ * - markdown content containing a bare ```{lang} fence-open line outside a
23
+ * wrapping plain fence is indistinguishable from a real code chunk (in
24
+ * Quarto itself too) and will re-parse as one
25
+ * - a markdown content line that is itself a `<!-- #| id: ... -->` marker
26
+ * will re-parse as a cell boundary
27
+ */
28
+ import { NotebookFormatAdapter } from './types';
29
+ export declare const qmdAdapter: NotebookFormatAdapter;
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ /**
3
+ * Quarto adapter (.qmd) — notebook-first.
4
+ *
5
+ * A .qmd is treated as a notebook that doesn't serialize outputs:
6
+ * - optional YAML front matter (kept opaque; jupyter/nebula keys normalized)
7
+ * - ```{lang ...} fences are code cells; `#| id: x` carries the cell id;
8
+ * all other `#|` chunk options stay in cell content verbatim (they are
9
+ * valid comments) and are never interpreted
10
+ * - prose between fences becomes markdown cells
11
+ *
12
+ * No Quarto rendering, no chunk-option semantics, one kernel per document.
13
+ *
14
+ * Markdown cells are delimited by an HTML-comment marker mirroring the
15
+ * chunk-option syntax: `<!-- #| id: abc -->`. HTML comments are invisible in
16
+ * rendered Quarto output, so this is notebook-first without rendering cost:
17
+ * adjacent markdown cells stay distinct, empty markdown cells survive, and
18
+ * markdown ids persist (no positional fallback for Nebula-written files).
19
+ * Foreign .qmd files without markers still parse (one implicit markdown cell
20
+ * per prose block, positional ids); markers appear on first Nebula save.
21
+ *
22
+ * Known unrepresentables (inherent):
23
+ * - markdown content containing a bare ```{lang} fence-open line outside a
24
+ * wrapping plain fence is indistinguishable from a real code chunk (in
25
+ * Quarto itself too) and will re-parse as one
26
+ * - a markdown content line that is itself a `<!-- #| id: ... -->` marker
27
+ * will re-parse as a cell boundary
28
+ */
29
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ var desc = Object.getOwnPropertyDescriptor(m, k);
32
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
33
+ desc = { enumerable: true, get: function() { return m[k]; } };
34
+ }
35
+ Object.defineProperty(o, k2, desc);
36
+ }) : (function(o, m, k, k2) {
37
+ if (k2 === undefined) k2 = k;
38
+ o[k2] = m[k];
39
+ }));
40
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
41
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
42
+ }) : function(o, v) {
43
+ o["default"] = v;
44
+ });
45
+ var __importStar = (this && this.__importStar) || (function () {
46
+ var ownKeys = function(o) {
47
+ ownKeys = Object.getOwnPropertyNames || function (o) {
48
+ var ar = [];
49
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
50
+ return ar;
51
+ };
52
+ return ownKeys(o);
53
+ };
54
+ return function (mod) {
55
+ if (mod && mod.__esModule) return mod;
56
+ var result = {};
57
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
58
+ __setModuleDefault(result, mod);
59
+ return result;
60
+ };
61
+ })();
62
+ Object.defineProperty(exports, "__esModule", { value: true });
63
+ exports.qmdAdapter = void 0;
64
+ const YAML = __importStar(require("yaml"));
65
+ const CODE_FENCE_OPEN_RE = /^(`{3,})\{(\w+)([^}]*)\}\s*$/;
66
+ const PLAIN_FENCE_RE = /^(`{3,})(?!\{)/;
67
+ const ID_OPTION_RE = /^#\|\s*id:\s*(\S+)\s*$/;
68
+ const MD_MARKER_RE = /^<!--\s*#\|\s*id:\s*(\S+)\s*-->\s*$/;
69
+ const LANG_TO_KERNEL = {
70
+ python: 'python3',
71
+ r: 'ir',
72
+ };
73
+ function normalizeEol(text) {
74
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
75
+ }
76
+ function parseFrontMatter(lines) {
77
+ if (lines[0] !== '---')
78
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
79
+ let end = -1;
80
+ for (let i = 1; i < lines.length; i++) {
81
+ if (lines[i] === '---' || lines[i] === '...') {
82
+ end = i;
83
+ break;
84
+ }
85
+ }
86
+ if (end === -1)
87
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
88
+ let fm;
89
+ try {
90
+ const parsed = YAML.parse(lines.slice(1, end).join('\n'));
91
+ fm = parsed && typeof parsed === 'object' ? parsed : {};
92
+ }
93
+ catch {
94
+ return { metadata: {}, kernelspecName: null, bodyStart: 0 };
95
+ }
96
+ const metadata = { __qmd_front_matter: fm };
97
+ let kernelspecName = null;
98
+ const jupyter = fm.jupyter;
99
+ if (typeof jupyter === 'string') {
100
+ kernelspecName = jupyter;
101
+ metadata.kernelspec = { name: jupyter };
102
+ }
103
+ else if (jupyter && typeof jupyter === 'object') {
104
+ const kernelspec = jupyter.kernelspec;
105
+ if (kernelspec && typeof kernelspec === 'object') {
106
+ metadata.kernelspec = kernelspec;
107
+ const name = kernelspec.name;
108
+ if (typeof name === 'string')
109
+ kernelspecName = name;
110
+ }
111
+ }
112
+ if (fm.nebula && typeof fm.nebula === 'object') {
113
+ metadata.nebula = fm.nebula;
114
+ }
115
+ let bodyStart = end + 1;
116
+ if (lines[bodyStart] === '')
117
+ bodyStart++;
118
+ return { metadata, kernelspecName, bodyStart };
119
+ }
120
+ function makeMarkdownCell(index, bodyLines, explicitId) {
121
+ // Trim one leading and one trailing blank line (block separators)
122
+ const body = [...bodyLines];
123
+ if (body[0] === '')
124
+ body.shift();
125
+ if (body[body.length - 1] === '')
126
+ body.pop();
127
+ // Implicit (marker-less, foreign-file) prose blocks that are empty are
128
+ // just whitespace between chunks; marker-delimited cells survive empty.
129
+ if (body.length === 0 && explicitId === null)
130
+ return null;
131
+ return {
132
+ id: explicitId ?? `cell-${index}`,
133
+ type: 'markdown',
134
+ content: body.join('\n'),
135
+ outputs: [],
136
+ executionCount: null,
137
+ isExecuting: false,
138
+ };
139
+ }
140
+ function parse(text) {
141
+ const lines = normalizeEol(text).split('\n');
142
+ if (lines.length > 0 && lines[lines.length - 1] === '')
143
+ lines.pop();
144
+ const { metadata, kernelspecName, bodyStart } = parseFrontMatter(lines);
145
+ const cells = [];
146
+ let prose = [];
147
+ let proseId = null;
148
+ let firstLanguage = null;
149
+ const flushProse = () => {
150
+ const cell = makeMarkdownCell(cells.length, prose, proseId);
151
+ if (cell)
152
+ cells.push(cell);
153
+ prose = [];
154
+ proseId = null;
155
+ };
156
+ let i = bodyStart;
157
+ while (i < lines.length) {
158
+ const line = lines[i];
159
+ const codeOpen = line.match(CODE_FENCE_OPEN_RE);
160
+ if (codeOpen) {
161
+ flushProse();
162
+ const ticks = codeOpen[1].length;
163
+ const language = codeOpen[2];
164
+ const fenceInner = `${codeOpen[2]}${codeOpen[3]}`;
165
+ if (!firstLanguage)
166
+ firstLanguage = language;
167
+ const body = [];
168
+ i++;
169
+ let closed = false;
170
+ while (i < lines.length) {
171
+ if (new RegExp(`^\`{${ticks},}\\s*$`).test(lines[i])) {
172
+ closed = true;
173
+ i++;
174
+ break;
175
+ }
176
+ body.push(lines[i]);
177
+ i++;
178
+ }
179
+ void closed; // unterminated fence: treat rest of file as the cell body
180
+ // Consume a leading `#| id:` option as the cell id
181
+ let id = null;
182
+ if (body.length > 0) {
183
+ const idMatch = body[0].match(ID_OPTION_RE);
184
+ if (idMatch) {
185
+ id = idMatch[1];
186
+ body.shift();
187
+ }
188
+ }
189
+ const cell = {
190
+ id: id ?? `cell-${cells.length}`,
191
+ type: 'code',
192
+ content: body.join('\n'),
193
+ outputs: [],
194
+ executionCount: null,
195
+ isExecuting: false,
196
+ };
197
+ cell._metadata = {
198
+ qmd_fence_attrs: fenceInner,
199
+ };
200
+ cells.push(cell);
201
+ // Skip one blank separator after the closing fence
202
+ if (lines[i] === '')
203
+ i++;
204
+ continue;
205
+ }
206
+ const mdMarker = line.match(MD_MARKER_RE);
207
+ if (mdMarker) {
208
+ flushProse();
209
+ proseId = mdMarker[1];
210
+ i++;
211
+ continue;
212
+ }
213
+ const plainFence = line.match(PLAIN_FENCE_RE);
214
+ if (plainFence) {
215
+ // A plain fence inside prose: copy verbatim until its closer so that
216
+ // ```{lang} examples inside it are never mistaken for cell starts.
217
+ const ticks = plainFence[1].length;
218
+ prose.push(line);
219
+ i++;
220
+ while (i < lines.length) {
221
+ prose.push(lines[i]);
222
+ if (new RegExp(`^\`{${ticks},}\\s*$`).test(lines[i])) {
223
+ i++;
224
+ break;
225
+ }
226
+ i++;
227
+ }
228
+ continue;
229
+ }
230
+ prose.push(line);
231
+ i++;
232
+ }
233
+ flushProse();
234
+ if (firstLanguage)
235
+ metadata.__qmd_language = firstLanguage;
236
+ let resolvedKernel = kernelspecName;
237
+ if (!resolvedKernel && firstLanguage) {
238
+ resolvedKernel = LANG_TO_KERNEL[firstLanguage.toLowerCase()] ?? null;
239
+ }
240
+ return { cells, metadata, kernelspecName: resolvedKernel };
241
+ }
242
+ function serializeFrontMatter(metadata, kernelName) {
243
+ const base = (metadata.__qmd_front_matter && typeof metadata.__qmd_front_matter === 'object'
244
+ ? { ...metadata.__qmd_front_matter }
245
+ : {});
246
+ const kernelspec = (metadata.kernelspec && typeof metadata.kernelspec === 'object'
247
+ ? { ...metadata.kernelspec }
248
+ : {});
249
+ if (kernelName)
250
+ kernelspec.name = kernelName;
251
+ if (Object.keys(kernelspec).length > 0) {
252
+ const jupyter = (base.jupyter && typeof base.jupyter === 'object'
253
+ ? { ...base.jupyter }
254
+ : {});
255
+ jupyter.kernelspec = kernelspec;
256
+ base.jupyter = jupyter;
257
+ }
258
+ if (metadata.nebula && typeof metadata.nebula === 'object') {
259
+ base.nebula = metadata.nebula;
260
+ }
261
+ else {
262
+ delete base.nebula;
263
+ }
264
+ if (Object.keys(base).length === 0)
265
+ return [];
266
+ const yamlText = YAML.stringify(base, { lineWidth: 0 }).trimEnd();
267
+ return ['---', ...yamlText.split('\n'), '---'];
268
+ }
269
+ function serialize(cells, metadata, kernelName) {
270
+ const language = typeof metadata.__qmd_language === 'string' ? metadata.__qmd_language : 'python';
271
+ const out = serializeFrontMatter(metadata, kernelName);
272
+ for (const cell of cells) {
273
+ if (out.length > 0)
274
+ out.push('');
275
+ if (cell.type === 'markdown') {
276
+ out.push(`<!-- #| id: ${cell.id} -->`);
277
+ if (cell.content !== '')
278
+ out.push(...cell.content.split('\n'));
279
+ }
280
+ else {
281
+ const meta = cell._metadata ?? {};
282
+ const fenceInner = typeof meta.qmd_fence_attrs === 'string' ? meta.qmd_fence_attrs : language;
283
+ // Fence must be longer than any backtick run starting a content line,
284
+ // or the content would close it early.
285
+ let maxRun = 0;
286
+ for (const l of cell.content.split('\n')) {
287
+ const run = l.match(/^(`+)/);
288
+ if (run)
289
+ maxRun = Math.max(maxRun, run[1].length);
290
+ }
291
+ const fence = '`'.repeat(Math.max(3, maxRun + 1));
292
+ out.push(fence + '{' + fenceInner + '}');
293
+ out.push(`#| id: ${cell.id}`);
294
+ if (cell.content !== '')
295
+ out.push(...cell.content.split('\n'));
296
+ out.push(fence);
297
+ }
298
+ }
299
+ return out.join('\n') + '\n';
300
+ }
301
+ exports.qmdAdapter = {
302
+ name: 'qmd',
303
+ extensions: ['.qmd'],
304
+ capabilities: { storesOutputs: false, storesCellIds: true },
305
+ parse,
306
+ serialize,
307
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Registry for plain-text notebook format adapters.
3
+ *
4
+ * .ipynb is intentionally NOT registered here: dispatch sites check .ipynb
5
+ * first and fall through to the existing Jupyter JSON code path untouched.
6
+ */
7
+ import { NotebookFormatAdapter } from './types';
8
+ export declare function getFormatAdapter(filePath: string): NotebookFormatAdapter | null;
9
+ export declare function defaultKernelForPath(filePath: string): string;
10
+ export declare function isTextNotebookPath(filePath: string): boolean;
11
+ /** True for any path Nebula can treat as a notebook (.ipynb or text format). */
12
+ export declare function isNotebookPath(filePath: string): boolean;