Skip to content

Commit d6ddcf5

Browse files
committed
feat: add initial tool to convert IR to ipynb
Ref: stdlib-js/metr-issue-tracker#1291 --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown_pkg_readmes status: passed - task: lint_markdown_docs status: na - task: lint_markdown status: na - task: lint_package_json status: passed - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: passed - task: lint_javascript_tests status: passed - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent 7be6477 commit d6ddcf5

29 files changed

Lines changed: 5629 additions & 0 deletions
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<!--
2+
3+
@license Apache-2.0
4+
5+
Copyright (c) 2026 The Stdlib Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
19+
-->
20+
21+
# toIPYNB
22+
23+
> Convert a semantic document to a Jupyter notebook targeting the JupyterLite JavaScript kernel.
24+
25+
<section class="intro">
26+
27+
The converter produces deterministic nbformat 4.5 notebook objects from semantic document v1.0.0 input. It targets the browser-based [JupyterLite JavaScript kernel][jupyterlite-javascript-kernel] and defaults to its Web Worker runtime.
28+
29+
</section>
30+
31+
<!-- /.intro -->
32+
33+
<section class="usage">
34+
35+
## Usage
36+
37+
```javascript
38+
var fromMarkdown = require( '@stdlib/_tools/semantic-document/from-markdown' );
39+
var toIPYNB = require( '@stdlib/_tools/semantic-document/to-ipynb' );
40+
41+
var doc = fromMarkdown( '# Example\n\n> A notebook example.', {
42+
'profile': 'tutorial'
43+
});
44+
var notebook = toIPYNB( doc );
45+
var json = toIPYNB.stringify( notebook );
46+
```
47+
48+
### toIPYNB( doc\[, options] )
49+
50+
Returns a fresh nbformat 4.5 notebook object. The function validates the semantic document, preserves semantic provenance in namespaced cell metadata, assigns deterministic cell IDs, and validates the generated notebook.
51+
52+
If conversion fails after notebook construction begins, the thrown error exposes the completed prefix as `error.notebook`. Strict rejection of producer error diagnostics exposes a root notebook with metadata and no cells. Schema-, version-, and reference-integrity-invalid semantic documents are rejected before notebook construction and therefore do not expose a partial notebook.
53+
54+
The default target is the JupyterLite Web Worker kernel:
55+
56+
```javascript
57+
var workerNotebook = toIPYNB( doc, {
58+
'target': 'jupyterlite-worker'
59+
});
60+
```
61+
62+
To select the less-isolated IFrame kernel, set `target` to `'jupyterlite-iframe'`. The converter only records the requested kernel. A JupyterLite deployment must install and configure [`jupyterlite-javascript-kernel`][jupyterlite-javascript-kernel] when building the site.
63+
64+
The adapter metadata intentionally does not claim tested JupyterLite package versions yet. Before release, the browser compatibility suite must pass against an exact `jupyterlite-core` and `jupyterlite-javascript-kernel` pair; only that verified pair should then be recorded in the preset.
65+
66+
The following options are supported:
67+
68+
- **excludeSectionRoles**: semantic section roles whose complete subtrees should be omitted. Default: `[ 'c', 'cli' ]`.
69+
- **mode**: conversion mode. Must be `'strict'` or `'permissive'`. Default: `'strict'`.
70+
- **target**: complete JupyterLite adapter preset. Must be `'jupyterlite-worker'` or `'jupyterlite-iframe'`. Default: `'jupyterlite-worker'`.
71+
- **moduleMap**: explicit module-resolution overlay described below.
72+
- **unsupportedCode**: disposition for an enabled execution candidate which is not browser eligible. Must be `'error'` or `'markdown'`. By default, strict mode uses `'error'` and permissive mode uses `'markdown'`.
73+
- **includeProvenance**: boolean indicating whether to copy detailed semantic source provenance to cell metadata. Default: `false`.
74+
75+
By default, the converter removes C API and command-line interface sections which cannot be used interactively with the JavaScript kernel. Filtering uses semantic `section.role` values rather than heading text. To retain every section, provide an empty exclusion array:
76+
77+
```javascript
78+
var completeNotebook = toIPYNB( doc, {
79+
'excludeSectionRoles': []
80+
});
81+
```
82+
83+
Each excluded subtree is recorded under `notebook.metadata.stdlib.omissions` with its semantic ID, role, key, tree hash, and exclusion reason. If filtering makes two semantic thematic breaks consecutive, the converter retains the later rule associated with the surviving content and records the earlier rule as a `redundant-thematic-break` omission. Authored consecutive rules which were not joined by section filtering are preserved. The input semantic document is not modified.
84+
85+
Enabled JavaScript candidates are parsed as ES2017 modules and checked conservatively for browser-incompatible dependencies and free globals. Direct, safe, top-level CommonJS bindings for canonical `@stdlib/*` packages are lowered according to stdlib's standalone ESM repository convention. For example,
86+
87+
```javascript
88+
var ndarray = require( '@stdlib/ndarray/ctor' );
89+
```
90+
91+
becomes
92+
93+
```text
94+
import ndarray from 'https://cdn.jsdelivr.net/gh/stdlib-js/ndarray-ctor@esm/index.mjs';
95+
```
96+
97+
The complete pre-transform source and ordered transform records are retained in `cell.metadata.stdlib`.
98+
99+
This eligibility check is conservative static analysis, not a security sandbox or a guarantee that eligible code will terminate or run successfully.
100+
101+
#### moduleMap
102+
103+
`moduleMap` is a closed, explicit overlay on convention-based module resolution. It has two main uses:
104+
105+
1. redirect a module to a deployment-controlled, often same-origin asset; and
106+
2. describe a non-stdlib module or an exceptional export shape.
107+
108+
It is not a general import map and does not enable URL synthesis. Each record must declare the exact URL, export shape, supported runtimes, and whether the URL is mutable:
109+
110+
```javascript
111+
var mappedNotebook = toIPYNB( doc, {
112+
'moduleMap': {
113+
'@stdlib/ndarray/ctor': {
114+
'url': '/assets/ndarray-ctor/index.mjs',
115+
'export': 'default',
116+
'runtimes': [ 'worker', 'iframe' ],
117+
'mutable': false
118+
}
119+
}
120+
});
121+
```
122+
123+
URLs must be HTTPS or root-absolute same-origin URLs. Relative, protocol-relative, credential-bearing, malformed, whitespace-containing, `data:`, `blob:`, and `javascript:` URLs are rejected. A module-map entry is an explicit caller assertion; the converter does not fetch or verify it. Transform metadata marks the resolver source as `module-map`.
124+
125+
### toIPYNB.stringify( notebook )
126+
127+
Serializes a notebook using recursively sorted object keys, one-space indentation, and a final newline suitable for an `*.ipynb` file.
128+
129+
</section>
130+
131+
<!-- /.usage -->
132+
133+
<section class="notes">
134+
135+
## Notes
136+
137+
- Disabled execution candidates are emitted as dedicated Markdown cells. The converter does not rely on a notebook tag to prevent execution.
138+
- Expected-error code cells receive the advisory `raises-exception` tag. Interactive JupyterLite execution does not enforce that tag.
139+
- Worker code may produce rich visual output through standard Jupyter MIME bundles; it does not need direct DOM access for normal plots.
140+
- The IFrame target permits direct DOM globals and consequently executes with a stronger page-integrity risk than the worker target.
141+
- Standalone media requires exact semantic source slices in v1. Strict conversion rejects media without them; permissive conversion emits a visible placeholder and diagnostic.
142+
- Section-role exclusions remove the complete matching subtree. Nested usage, examples, resources, and display-only code belonging to an excluded C or CLI section are not promoted elsewhere in the notebook.
143+
- Convention-derived `@esm` URLs are mutable and network-dependent. Offline or reproducible deployments should supply reviewed, immutable same-origin module-map entries whose transitive dependency graphs are also vendored.
144+
- Canonical stdlib resolution assumes the standalone `index.mjs` default-export convention and derives support for both JupyterLite runtimes. The converter does not maintain a duplicate package compatibility database or promise that a remote module and its transitive dependencies will load successfully.
145+
146+
</section>
147+
148+
<!-- /.notes -->
149+
150+
<section class="examples">
151+
152+
## Examples
153+
154+
```javascript
155+
var exampleFromMarkdown = require( '@stdlib/_tools/semantic-document/from-markdown' );
156+
var exampleToIPYNB = require( '@stdlib/_tools/semantic-document/to-ipynb' );
157+
158+
var markdown = [
159+
'# Example',
160+
'',
161+
'> Compute an absolute value.',
162+
'',
163+
'```javascript',
164+
'var abs = require( \'@stdlib/math/base/special/abs\' );',
165+
'```'
166+
].join( '\n' );
167+
168+
var exampleDoc = exampleFromMarkdown( markdown, {
169+
'profile': 'tutorial'
170+
});
171+
var exampleNotebook = exampleToIPYNB( exampleDoc );
172+
173+
console.log( exampleToIPYNB.stringify( exampleNotebook ) );
174+
```
175+
176+
</section>
177+
178+
<!-- /.examples -->
179+
180+
<section class="links">
181+
182+
[jupyterlite-javascript-kernel]: https://github.com/jupyterlite/javascript-kernel
183+
184+
</section>
185+
186+
<!-- /.links -->

0 commit comments

Comments
 (0)