diff --git a/src/core/jupyter/jupyter-filters.ts b/src/core/jupyter/jupyter-filters.ts index 01f74f3c14..96787f16a7 100644 --- a/src/core/jupyter/jupyter-filters.ts +++ b/src/core/jupyter/jupyter-filters.ts @@ -13,16 +13,20 @@ import { Format } from "../../config/types.ts"; import { execProcess } from "../../core/process.ts"; import { handlerForScript } from "../../core/run/run.ts"; import { parseShellRunCommand } from "../../core/run/shell.ts"; +import { JupyterNotebook } from "./types.ts"; -export async function markdownFromNotebook(file: string, format?: Format) { +export async function markdownFromNotebookFile(file: string, format?: Format) { // read file with any filters const nbContents = await jupyterNotebookFiltered( file, format?.execute[kIpynbFilters], ); const nb = JSON.parse(nbContents); - const cells = nb.cells as Array<{ cell_type: string; source: string[] }>; - const markdown = cells.reduce((md, cell) => { + return markdownFromNotebookJSON(nb); +} + +export function markdownFromNotebookJSON(nb: JupyterNotebook) { + const markdown = nb.cells.reduce((md, cell) => { if (["markdown", "raw"].includes(cell.cell_type)) { return md + "\n" + cell.source.join("") + "\n"; } else { diff --git a/src/execute/jupyter/jupyter-kernel.ts b/src/execute/jupyter/jupyter-kernel.ts index 5f98cf99e1..f8544aac41 100644 --- a/src/execute/jupyter/jupyter-kernel.ts +++ b/src/execute/jupyter/jupyter-kernel.ts @@ -15,7 +15,10 @@ import { execProcess, ProcessResult } from "../../core/process.ts"; import { md5Hash } from "../../core/hash.ts"; import { resourcePath } from "../../core/resources.ts"; import { pythonExec } from "../../core/jupyter/exec.ts"; -import { JupyterCapabilities } from "../../core/jupyter/types.ts"; +import { + JupyterCapabilities, + JupyterKernelspec, +} from "../../core/jupyter/types.ts"; import { jupyterCapabilities } from "../../core/jupyter/capabilities.ts"; import { jupyterCapabilitiesMessage, @@ -33,6 +36,7 @@ import { import { ExecuteOptions } from "../types.ts"; export interface JupyterExecuteOptions extends ExecuteOptions { + kernelspec: JupyterKernelspec; python_cmd: string[]; } @@ -44,7 +48,7 @@ export async function executeKernelOneshot( // execute the notebook (save back in place) if (!options.quiet) { - messageStartingKernel(); + messageStartingKernel(options.kernelspec); } trace(options, "Executing notebook with oneshot kernel"); @@ -373,7 +377,7 @@ async function connectToKernel( // start the kernel if (!options.quiet) { - messageStartingKernel(); + messageStartingKernel(options.kernelspec); } // determine timeout @@ -446,8 +450,8 @@ async function denoConnectToKernel( } } -function messageStartingKernel() { - info("\nStarting Jupyter kernel...", { newline: false }); +function messageStartingKernel(kernelspec: JupyterKernelspec) { + info(`\nStarting ${kernelspec.name} kernel...`, { newline: false }); } function trace(options: ExecuteOptions, msg: string) { diff --git a/src/execute/jupyter/jupyter.ts b/src/execute/jupyter/jupyter.ts index 514148402e..33bfe873e1 100644 --- a/src/execute/jupyter/jupyter.ts +++ b/src/execute/jupyter/jupyter.ts @@ -53,7 +53,11 @@ import { executeKernelOneshot, JupyterExecuteOptions, } from "./jupyter-kernel.ts"; -import { JupyterWidgetDependencies } from "../../core/jupyter/types.ts"; +import { + JupyterKernelspec, + JupyterNotebook, + JupyterWidgetDependencies, +} from "../../core/jupyter/types.ts"; import { includesForJupyterWidgetDependencies, } from "../../core/jupyter/widgets.ts"; @@ -75,10 +79,11 @@ import { pythonExec } from "../../core/jupyter/exec.ts"; import { jupyterNotebookFiltered, - markdownFromNotebook, + markdownFromNotebookFile, + markdownFromNotebookJSON, } from "../../core/jupyter/jupyter-filters.ts"; import { asMappedString } from "../../core/lib/mapped-text.ts"; -import { mappedStringFromFile } from "../../core/mapped-text.ts"; +import { MappedString, mappedStringFromFile } from "../../core/mapped-text.ts"; import { breakQuartoMd } from "../../core/lib/break-quarto-md.ts"; export const jupyterEngine: ExecutionEngine = { @@ -121,9 +126,16 @@ export const jupyterEngine: ExecutionEngine = { target: async ( file: string, ): Promise => { - const markdown = isJupyterNotebook(file) - ? asMappedString(await markdownFromNotebook(file)) // FIXME how do we do source mapping from .ipynb? - : asMappedString(mappedStringFromFile(file)); + // at some point we'll resolve a full notebook/kernelspec + let nb: JupyterNotebook | undefined; + let markdown: MappedString; + if (isJupyterNotebook(file)) { + const nbJSON = Deno.readTextFileSync(file); + nb = JSON.parse(nbJSON) as JupyterNotebook; + markdown = asMappedString(markdownFromNotebookJSON(nb)); + } else { + markdown = asMappedString(mappedStringFromFile(file)); + } // get the metadata const metadata = readYamlFromMarkdown(markdown.value); @@ -138,9 +150,10 @@ export const jupyterEngine: ExecutionEngine = { input: notebook, markdown, metadata, - data: { transient: true }, + data: { transient: true, kernelspec: {} }, }; - await createNotebookforTarget(target); + nb = await createNotebookforTarget(target); + target.data.kernelspec = nb.metadata.kernelspec; return target; } else if (isJupyterNotebook(file)) { return { @@ -148,7 +161,7 @@ export const jupyterEngine: ExecutionEngine = { input: file, markdown, metadata, - data: { transient: false }, + data: { transient: false, kernelspec: nb?.metadata.kernelspec }, }; } else { return undefined; @@ -157,7 +170,7 @@ export const jupyterEngine: ExecutionEngine = { partitionedMarkdown: async (file: string, format?: Format) => { if (isJupyterNotebook(file)) { - return partitionMarkdown(await markdownFromNotebook(file, format)); + return partitionMarkdown(await markdownFromNotebookFile(file, format)); } else { return partitionMarkdown(Deno.readTextFileSync(file)); } @@ -215,6 +228,9 @@ export const jupyterEngine: ExecutionEngine = { await createNotebookforTarget(options.target); } + // determine the kernel (it's in the custom execute options data) + const kernelspec = (options.target.data as JupyterTargetData).kernelspec; + // determine execution behavior const execute = options.format.execute[kExecuteEnabled] !== false; if (execute) { @@ -240,6 +256,7 @@ export const jupyterEngine: ExecutionEngine = { } } const jupyterExecOptions: JupyterExecuteOptions = { + kernelspec, python_cmd: await pythonExec(), ...execOptions, }; @@ -368,6 +385,7 @@ function isQmdFile(file: string) { async function createNotebookforTarget(target: ExecutionTarget) { const nb = await quartoMdToJupyter(target.source, true); Deno.writeTextFileSync(target.input, JSON.stringify(nb, null, 2)); + return nb; } async function disableDaemonForNotebook(target: ExecutionTarget) { @@ -416,6 +434,7 @@ function cleanupNotebook(target: ExecutionTarget, format: Format) { interface JupyterTargetData { transient: boolean; + kernelspec: JupyterKernelspec; } function executeResultIncludes(