@lincyaw/render-notebook 1.0.0-rc.0 → 1.0.0-rc.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/README.md +3 -3
- package/package.json +6 -6
- package/src/domain/markdown.ts +2 -1
- package/src/domain/messages.ts +18 -0
- package/src/domain/notebook.ts +41 -40
- package/src/node/presmium.ts +24 -24
- package/src/node/.gitkeep +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Presmium's notebook renderer: writes the IR as a Jupyter notebook (nbformat 4). Narration becomes Markdown cells, code in the kernel's language becomes code cells with their recorded outputs, and components show their fallback.
|
|
4
4
|
|
|
5
|
-
Part of [Presmium](https://github.com/Lincyaw/presmium), which
|
|
5
|
+
Part of [Presmium](https://github.com/Lincyaw/presmium), which compiles canonical lecture.json teaching documents into web pages, slides, Jupyter notebooks and narrated playback.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,11 +12,11 @@ pnpm add -D @lincyaw/cli @lincyaw/render-notebook
|
|
|
12
12
|
|
|
13
13
|
## Configuration
|
|
14
14
|
|
|
15
|
-
In
|
|
15
|
+
In `lecture.json`, `metadata.renderers.notebook`: `kernel` (default `python3`) and `webUrl`, where components the notebook cannot show link to (by default `../web/` when the web pages are built too).
|
|
16
16
|
|
|
17
17
|
## Documentation
|
|
18
18
|
|
|
19
|
-
- [Author guide](https://github.com/Lincyaw/presmium/blob/main/site/guide/lecture.
|
|
19
|
+
- [Author guide](https://github.com/Lincyaw/presmium/blob/main/site/guide/locales/en/lecture.json)
|
|
20
20
|
|
|
21
21
|
## License
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincyaw/render-notebook",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "Presmium's notebook renderer: writes the IR as a Jupyter notebook (nbformat 4)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"vfile": "^5.3.7"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@lincyaw/kernel": "^1.0.0-rc.
|
|
39
|
-
"@lincyaw/web": "^1.0.0-rc.
|
|
38
|
+
"@lincyaw/kernel": "^1.0.0-rc.2",
|
|
39
|
+
"@lincyaw/web": "^1.0.0-rc.2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
42
|
"@lincyaw/web": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@lincyaw/cli": "1.0.0-rc.
|
|
48
|
-
"@lincyaw/kernel": "1.0.0-rc.
|
|
49
|
-
"@lincyaw/web": "1.0.0-rc.
|
|
47
|
+
"@lincyaw/cli": "1.0.0-rc.2",
|
|
48
|
+
"@lincyaw/kernel": "1.0.0-rc.2",
|
|
49
|
+
"@lincyaw/web": "1.0.0-rc.2"
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/domain/markdown.ts
CHANGED
|
@@ -9,7 +9,8 @@ export interface MarkdownText {
|
|
|
9
9
|
degraded: string[]
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** A Markdown tree as Markdown a notebook shows: MyST lowered to what Jupyter renders, then written block by block. */
|
|
13
|
+
export function lowerToMarkdown(mdast: MdastNode, urls?: LowerOptions['urls']): MarkdownText {
|
|
13
14
|
const lowered = lowerMyst(mdast, { urls, htmlUrls: true })
|
|
14
15
|
const problems: string[] = []
|
|
15
16
|
const parts = (lowered.mdast.children ?? []).map((block) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Locale, MdastNode } from '@lincyaw/kernel'
|
|
2
|
+
|
|
3
|
+
function text(value: string): MdastNode {
|
|
4
|
+
return { type: 'text', value }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const NOTEBOOK_MESSAGES = {
|
|
8
|
+
'zh-CN': {
|
|
9
|
+
web: '网页版',
|
|
10
|
+
note: (name: MdastNode, web: MdastNode): MdastNode[] => [text('交互组件 '), name, text(' 需要在'), web, text('中查看。')],
|
|
11
|
+
},
|
|
12
|
+
'en': {
|
|
13
|
+
web: 'the web version',
|
|
14
|
+
note: (name: MdastNode, web: MdastNode): MdastNode[] => [text('The interactive component '), name, text(' can only be used in '), web, text('.')],
|
|
15
|
+
},
|
|
16
|
+
} satisfies Record<Locale, unknown>
|
|
17
|
+
|
|
18
|
+
export type NotebookMessages = (typeof NOTEBOOK_MESSAGES)[Locale]
|
package/src/domain/notebook.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errorMessage, isRecord, languageId,
|
|
1
|
+
import { errorMessage, hasRendererExtension, isRecord, languageId, withPublishedAssets } from '@lincyaw/kernel'
|
|
2
2
|
import type {
|
|
3
3
|
CodeNode,
|
|
4
4
|
ComponentNode,
|
|
@@ -9,20 +9,23 @@ import type {
|
|
|
9
9
|
Locale,
|
|
10
10
|
MdastNode,
|
|
11
11
|
RecordedRun,
|
|
12
|
-
|
|
12
|
+
BoundRendererExtension,
|
|
13
13
|
RunOutput,
|
|
14
14
|
SourcePos,
|
|
15
15
|
Support,
|
|
16
16
|
} from '@lincyaw/kernel'
|
|
17
|
-
import {
|
|
17
|
+
import { lowerToMarkdown } from './markdown.ts'
|
|
18
|
+
import { NOTEBOOK_MESSAGES } from './messages.ts'
|
|
19
|
+
import type { NotebookMessages } from './messages.ts'
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/** The Jupyter kernel a notebook names (nbformat's `kernelspec`). */
|
|
22
|
+
export interface JupyterKernelSpec {
|
|
20
23
|
name: string
|
|
21
24
|
display_name: string
|
|
22
25
|
language: string
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
export const
|
|
28
|
+
export const DEFAULT_JUPYTER_KERNEL: JupyterKernelSpec = { name: 'python3', display_name: 'Python 3', language: 'python' }
|
|
26
29
|
|
|
27
30
|
export type NotebookCellInput = string | {
|
|
28
31
|
cell_type: 'markdown' | 'code' | 'raw'
|
|
@@ -48,12 +51,12 @@ export interface NotebookCell {
|
|
|
48
51
|
export interface Notebook {
|
|
49
52
|
nbformat: 4
|
|
50
53
|
nbformat_minor: 5
|
|
51
|
-
metadata: { kernelspec:
|
|
54
|
+
metadata: { kernelspec: JupyterKernelSpec, language_info: { name: string }, title?: string }
|
|
52
55
|
cells: NotebookCell[]
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
export interface NotebookOptions {
|
|
56
|
-
|
|
59
|
+
jupyterKernel: JupyterKernelSpec
|
|
57
60
|
locale: Locale
|
|
58
61
|
webLink?: (section: IRSection) => string | undefined
|
|
59
62
|
extensionFor: (node: ComponentNode) => ComponentRenderer | undefined
|
|
@@ -70,47 +73,39 @@ export interface NotebookResult {
|
|
|
70
73
|
|
|
71
74
|
type ComponentRenderer = (node: ComponentNode, block: IRBlock) => unknown
|
|
72
75
|
|
|
73
|
-
const MESSAGES = {
|
|
74
|
-
'zh-CN': {
|
|
75
|
-
web: '网页版',
|
|
76
|
-
note: (name: MdastNode, web: MdastNode): MdastNode[] => [text('交互组件 '), name, text(' 需要在'), web, text('中查看。')],
|
|
77
|
-
},
|
|
78
|
-
'en': {
|
|
79
|
-
web: 'the web version',
|
|
80
|
-
note: (name: MdastNode, web: MdastNode): MdastNode[] => [text('The interactive component '), name, text(' can only be used in '), web, text('.')],
|
|
81
|
-
},
|
|
82
|
-
} satisfies Record<Locale, unknown>
|
|
83
|
-
|
|
84
76
|
function text(value: string): MdastNode {
|
|
85
77
|
return { type: 'text', value }
|
|
86
78
|
}
|
|
87
79
|
|
|
88
|
-
|
|
89
|
-
|
|
80
|
+
/** How well a notebook shows a node: a component without a notebook extension is degraded. */
|
|
81
|
+
const documentLayout = (node: IRNode): boolean => node.type === 'component' && node.plugin === 'presmium.layout' && ['PmColumns', 'PmStack', 'PmTabs'].includes(node.name)
|
|
82
|
+
export function notebookSupport(node: IRNode, extensions: BoundRendererExtension[]): Support {
|
|
83
|
+
if (documentLayout(node)) return 'full'
|
|
84
|
+
return node.type === 'component' && !hasRendererExtension(node, extensions) ? 'degraded' : 'full'
|
|
90
85
|
}
|
|
91
86
|
|
|
92
87
|
export function renderNotebook(doc: IRDocument, options: NotebookOptions): NotebookResult {
|
|
93
|
-
const {
|
|
88
|
+
const { jupyterKernel } = options
|
|
94
89
|
const out: NotebookResult = {
|
|
95
|
-
notebook: { nbformat: 4, nbformat_minor: 5, metadata: { kernelspec:
|
|
90
|
+
notebook: { nbformat: 4, nbformat_minor: 5, metadata: { kernelspec: jupyterKernel, language_info: { name: jupyterKernel.language } }, cells: [] },
|
|
96
91
|
skipped: [],
|
|
97
92
|
problems: [],
|
|
98
93
|
degraded: [],
|
|
99
94
|
unlinked: 0,
|
|
100
95
|
}
|
|
101
96
|
if (doc.meta.title) out.notebook.metadata.title = doc.meta.title
|
|
102
|
-
const
|
|
97
|
+
const cellCounts = new Map<string, number>()
|
|
103
98
|
const executions = { count: 0 }
|
|
104
|
-
for (const section of doc.sections) renderSection(section, options, out,
|
|
99
|
+
for (const section of doc.sections) renderSection(section, options, out, cellCounts, executions)
|
|
105
100
|
return out
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
function renderSection(section: IRSection, options: NotebookOptions, out: NotebookResult,
|
|
103
|
+
function renderSection(section: IRSection, options: NotebookOptions, out: NotebookResult, cellCounts: Map<string, number>, executions: { count: number }): void {
|
|
109
104
|
let pending: { parts: string[], blocks: string[] } | undefined
|
|
110
105
|
|
|
111
106
|
const cell = (type: NotebookCell['cell_type'], source: string | string[], blocks: string[], extra: Partial<NotebookCell> = {}) => {
|
|
112
107
|
out.notebook.cells.push({
|
|
113
|
-
id: cellId(section,
|
|
108
|
+
id: cellId(section, cellCounts),
|
|
114
109
|
cell_type: type,
|
|
115
110
|
...extra,
|
|
116
111
|
metadata: { ...extra.metadata, presmium: { section: section.id, blocks } },
|
|
@@ -123,7 +118,7 @@ function renderSection(section: IRSection, options: NotebookOptions, out: Notebo
|
|
|
123
118
|
}
|
|
124
119
|
const written = (mdast: MdastNode, block?: IRBlock): string => {
|
|
125
120
|
const { media } = options
|
|
126
|
-
const { text, problems, degraded } =
|
|
121
|
+
const { text, problems, degraded } = lowerToMarkdown(mdast, media && block ? url => media(url, block.source) : undefined)
|
|
127
122
|
const where = { section: section.id, block: block?.id ?? 'title', source: block?.source ?? section.source }
|
|
128
123
|
for (const problem of problems) out.problems.push({ message: `${where.section}#${where.block}: ${problem}`, source: where.source })
|
|
129
124
|
if (degraded.length) out.degraded.push({ ...where, nodes: degraded })
|
|
@@ -141,7 +136,7 @@ function renderSection(section: IRSection, options: NotebookOptions, out: Notebo
|
|
|
141
136
|
const shown = node.echo !== false
|
|
142
137
|
const caption = codeCaption(node)
|
|
143
138
|
if (caption && shown) markdown(caption, block)
|
|
144
|
-
if (languageId(node.lang) === languageId(options.
|
|
139
|
+
if (languageId(node.lang) === languageId(options.jupyterKernel.language)) {
|
|
145
140
|
flush()
|
|
146
141
|
const runs = node.outputs ?? []
|
|
147
142
|
const count = runs.length ? ++executions.count : null
|
|
@@ -163,10 +158,19 @@ function renderSection(section: IRSection, options: NotebookOptions, out: Notebo
|
|
|
163
158
|
|
|
164
159
|
const component = (original: ComponentNode, block: IRBlock) => {
|
|
165
160
|
const node = published(original, block, options)
|
|
161
|
+
if (documentLayout(node)) {
|
|
162
|
+
if (typeof node.props.title === 'string' && node.props.title) markdown({ type: 'heading', depth: 3, children: [text(node.props.title)] }, block)
|
|
163
|
+
for (const [index, child] of (node.children ?? []).entries()) {
|
|
164
|
+
const title = node.name === 'PmTabs' && Array.isArray(node.props.tabTitles) ? node.props.tabTitles[index] : undefined
|
|
165
|
+
if (typeof title === 'string' && title) markdown({ type: 'heading', depth: 4, children: [text(title)] }, child)
|
|
166
|
+
renderBlock(child)
|
|
167
|
+
}
|
|
168
|
+
return
|
|
169
|
+
}
|
|
166
170
|
const render = options.extensionFor(node)
|
|
167
171
|
if (render) {
|
|
168
172
|
try {
|
|
169
|
-
const cells =
|
|
173
|
+
const cells = toCellInputs(render(node, block))
|
|
170
174
|
if (cells) {
|
|
171
175
|
flush()
|
|
172
176
|
for (const input of cells) {
|
|
@@ -190,7 +194,7 @@ function renderSection(section: IRSection, options: NotebookOptions, out: Notebo
|
|
|
190
194
|
if (node.fallback) return markdown(node.fallback, block)
|
|
191
195
|
const link = options.webLink?.(section)
|
|
192
196
|
if (!link) out.unlinked++
|
|
193
|
-
markdown(webNote(node,
|
|
197
|
+
markdown(webNote(node, NOTEBOOK_MESSAGES[options.locale], link), block)
|
|
194
198
|
const reason = render ? `the notebook extension of ${node.name} rendered nothing and it has no fallback` : `component ${node.name} has no notebook extension or fallback`
|
|
195
199
|
out.skipped.push({ section: section.id, block: block.id, reason: `${reason}; a note points to the web version` })
|
|
196
200
|
node.children?.forEach(renderBlock)
|
|
@@ -210,13 +214,11 @@ function renderSection(section: IRSection, options: NotebookOptions, out: Notebo
|
|
|
210
214
|
|
|
211
215
|
function published(node: ComponentNode, block: IRBlock, options: NotebookOptions): ComponentNode {
|
|
212
216
|
const { media } = options
|
|
213
|
-
|
|
214
|
-
let props = node.props
|
|
215
|
-
for (const [path, url] of Object.entries(node.assets)) props = withProp(props, path, media(url, block.source) ?? url)
|
|
216
|
-
return { ...node, props }
|
|
217
|
+
return media ? withPublishedAssets(node, url => media(url, block.source)) : node
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
|
|
220
|
+
/** The cells a notebook extension returned; throws when one is not a cell. */
|
|
221
|
+
function toCellInputs(value: unknown): NotebookCellInput[] | undefined {
|
|
220
222
|
if (value === undefined || value === null) return undefined
|
|
221
223
|
const cells = Array.isArray(value) ? value : [value]
|
|
222
224
|
for (const c of cells) {
|
|
@@ -240,7 +242,7 @@ function codeCaption(node: CodeNode): MdastNode | undefined {
|
|
|
240
242
|
return children.length ? { type: 'paragraph', children } : undefined
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
function webNote(node: ComponentNode, t:
|
|
245
|
+
function webNote(node: ComponentNode, t: NotebookMessages, url?: string): MdastNode {
|
|
244
246
|
const web = url ? { type: 'link', url, children: [text(t.web)] } : text(t.web)
|
|
245
247
|
return { type: 'blockquote', children: [{ type: 'paragraph', children: t.note({ type: 'inlineCode', value: node.name }, web) }] }
|
|
246
248
|
}
|
|
@@ -269,10 +271,9 @@ function lines(text: string): string[] {
|
|
|
269
271
|
return text ? text.split(/(?<=\n)/) : []
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
function cellId(section: IRSection,
|
|
274
|
+
function cellId(section: IRSection, counts: Map<string, number>): string {
|
|
273
275
|
const base = section.id.replace(/[^\w-]/g, '').slice(0, 48) || `section-${section.index}`
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
ids.add(`${base}-${n}`)
|
|
276
|
+
const n = (counts.get(base) ?? 0) + 1
|
|
277
|
+
counts.set(base, n)
|
|
277
278
|
return `${base}-${n}`
|
|
278
279
|
}
|
package/src/node/presmium.ts
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import { createRequire } from 'node:module'
|
|
2
2
|
import { writeFile } from 'node:fs/promises'
|
|
3
3
|
import { basename, extname, join } from 'node:path'
|
|
4
|
-
import { deckLocale } from '@lincyaw/kernel'
|
|
4
|
+
import { deckLocale, WEB_RENDERER_PACKAGE } from '@lincyaw/kernel'
|
|
5
5
|
import type { IRDocument, IRSection, PresmiumNodePlugin, RenderContext } from '@lincyaw/kernel'
|
|
6
|
-
import {
|
|
7
|
-
import type {
|
|
6
|
+
import { DEFAULT_JUPYTER_KERNEL, notebookSupport, renderNotebook } from '../domain/notebook.ts'
|
|
7
|
+
import type { JupyterKernelSpec, NotebookOptions } from '../domain/notebook.ts'
|
|
8
8
|
import { localMedia } from './media.ts'
|
|
9
9
|
|
|
10
10
|
export type { NotebookCellInput } from '../domain/notebook.ts'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** The notebook renderer's plugin ID, which is also the ID of the checks it reports. */
|
|
13
|
+
const PLUGIN_ID = 'presmium.notebook'
|
|
13
14
|
const { version } = createRequire(import.meta.url)('../../package.json') as { version: string }
|
|
14
15
|
|
|
15
16
|
const plugin: PresmiumNodePlugin = {
|
|
16
|
-
id:
|
|
17
|
+
id: PLUGIN_ID,
|
|
17
18
|
version,
|
|
18
19
|
kernel: '^1.0.0',
|
|
19
20
|
renderer: {
|
|
20
21
|
id: 'notebook',
|
|
21
|
-
support,
|
|
22
|
+
support: notebookSupport,
|
|
22
23
|
async build(doc, ctx) {
|
|
23
|
-
const { notebook, skipped, problems, degraded, unlinked } = renderNotebook(doc, { ...await
|
|
24
|
-
for (const { message, source } of problems) ctx.report({ checkId:
|
|
24
|
+
const { notebook, skipped, problems, degraded, unlinked } = renderNotebook(doc, { ...await notebookOptions(doc, ctx), media: localMedia(ctx) })
|
|
25
|
+
for (const { message, source } of problems) ctx.report({ checkId: PLUGIN_ID, severity: 'warning', message, source: { file: source.file, line: source.line } })
|
|
25
26
|
for (const { section, block, nodes, source } of degraded) {
|
|
26
27
|
const message = `${section}#${block}: degraded in the notebook, which has no Jupyter form for ${nodes.join(', ')}; the text is kept`
|
|
27
|
-
ctx.report({ checkId:
|
|
28
|
+
ctx.report({ checkId: PLUGIN_ID, severity: 'warning', message, source: { file: source.file, line: source.line } })
|
|
28
29
|
}
|
|
29
30
|
if (unlinked > 0) {
|
|
30
|
-
ctx.report({ checkId:
|
|
31
|
+
ctx.report({ checkId: PLUGIN_ID, severity: 'info', message: `${unlinked} components the notebook cannot show are noted without a link to the web version; set renderers.notebook.webUrl to link them` })
|
|
31
32
|
}
|
|
32
33
|
const name = basename(doc.meta.entry, extname(doc.meta.entry)) || 'notebook'
|
|
33
34
|
await writeFile(join(ctx.outDir, `${name}.ipynb`), `${JSON.stringify(notebook, null, 1)}\n`)
|
|
@@ -38,32 +39,31 @@ const plugin: PresmiumNodePlugin = {
|
|
|
38
39
|
|
|
39
40
|
export default plugin
|
|
40
41
|
|
|
41
|
-
async function
|
|
42
|
-
const warn = (message: string) => ctx.report({ checkId:
|
|
42
|
+
async function notebookOptions(doc: IRDocument, ctx: RenderContext): Promise<Omit<NotebookOptions, 'media'>> {
|
|
43
|
+
const warn = (message: string) => ctx.report({ checkId: PLUGIN_ID, severity: 'warning', message })
|
|
43
44
|
const { kernel, webUrl } = ctx.config
|
|
44
45
|
if (webUrl !== undefined && typeof webUrl !== 'string') warn('renderers.notebook.webUrl must be a URL; components are not linked to the web version')
|
|
45
|
-
return {
|
|
46
|
+
return { jupyterKernel: jupyterKernelSpec(kernel, warn), locale: deckLocale(ctx.lang), webLink: await webLink(doc, ctx, webUrl), extensionFor: ctx.extensionFor }
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const WEB_PACKAGE = '@lincyaw/web'
|
|
49
|
-
|
|
50
49
|
async function webLink(doc: IRDocument, ctx: RenderContext, webUrl: unknown): Promise<((section: IRSection) => string) | undefined> {
|
|
51
50
|
if (typeof webUrl === 'string') return section => `${webUrl.replace(/#.*$/, '')}#${encodeURIComponent(section.id)}`
|
|
52
|
-
const web = doc.config.renderers.web?.package ??
|
|
53
|
-
if (!ctx.renderers.includes('web') || web !==
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
56
|
-
const links =
|
|
51
|
+
const web = doc.config.renderers.web?.package ?? WEB_RENDERER_PACKAGE
|
|
52
|
+
if (!ctx.renderers.includes('web') || web !== WEB_RENDERER_PACKAGE) return undefined
|
|
53
|
+
const webDomain = await import('@lincyaw/web/domain').catch(() => undefined)
|
|
54
|
+
if (!webDomain) return undefined
|
|
55
|
+
const links = webDomain.sectionLinks(webDomain.webPages(doc))
|
|
57
56
|
return section => `../web${(links[section.id] ?? '/').replace(/^\/(?=#|$)/, '/index.html')}`
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
/** The Jupyter kernel the configuration `renderers.notebook.kernel` names: a name, or `{ name, language, display_name }`. */
|
|
60
|
+
function jupyterKernelSpec(value: unknown, warn: (message: string) => void): JupyterKernelSpec {
|
|
61
|
+
if (value === undefined || value === DEFAULT_JUPYTER_KERNEL.name) return DEFAULT_JUPYTER_KERNEL
|
|
62
62
|
if (typeof value === 'string' && value) return { name: value, display_name: value, language: value.replace(/[-\d.]+$/, '') || value }
|
|
63
63
|
if (typeof value === 'object' && value !== null && 'name' in value && 'language' in value && typeof value.name === 'string' && typeof value.language === 'string') {
|
|
64
64
|
const displayName = 'display_name' in value && typeof value.display_name === 'string' ? value.display_name : value.name
|
|
65
65
|
return { name: value.name, display_name: displayName, language: value.language }
|
|
66
66
|
}
|
|
67
|
-
warn(`renderers.notebook.kernel must be a kernel name or { name, language, display_name }; using ${
|
|
68
|
-
return
|
|
67
|
+
warn(`renderers.notebook.kernel must be a kernel name or { name, language, display_name }; using ${DEFAULT_JUPYTER_KERNEL.name}`)
|
|
68
|
+
return DEFAULT_JUPYTER_KERNEL
|
|
69
69
|
}
|
package/src/node/.gitkeep
DELETED
|
File without changes
|