Path: blob/main/src/format/ipynb/format-ipynb.ts
12925 views
/*1* format-ipynb.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { basename, dirname, join } from "../../deno_ral/path.ts";7import { readPartials } from "../../command/render/template.ts";8import {9kCellFormat,10kCellRawMimeType,11kDefaultImageExtension,12kIPynbTitleBlockTemplate,13} from "../../config/constants.ts";14import { Format, PandocFlags } from "../../config/types.ts";15import {16jupyterFromFile,17kQuartoMimeType,18} from "../../core/jupyter/jupyter.ts";19import {20kApplicationRtf,21kRestructuredText,22kTextHtml,23kTextLatex,24} from "../../core/mime.ts";25import { formatResourcePath } from "../../core/resources.ts";26import { createFormat } from "../formats-shared.ts";2728import { decodeBase64 as base64decode } from "encoding/base64";29import {30JupyterOutput,31JupyterOutputDisplayData,32} from "../../core/jupyter/types.ts";3334export function ipynbTitleTemplatePath() {35return formatResourcePath(36"ipynb",37join("templates", "title-block.md"),38);39}4041export function ipynbFormat(): Format {42return createFormat("Jupyter", "ipynb", {43pandoc: {44standalone: true,45[kDefaultImageExtension]: "png",46},47formatExtras: (48input: string,49_markdown: string,50_flags: PandocFlags,51format: Format,52) => {53// Snag the p5455const resolveTemplate = () => {56// iPynbs have a special title-block template partial that they can provide57// to permit the customization of the title block58const titleTemplate = ipynbTitleTemplatePath();5960const partials = readPartials(format.metadata, dirname(input));61if (partials.length > 0) {62const userTitleTemplate = partials.find((part) => {63return basename(part) === "title-block.md";64});65if (userTitleTemplate) {66return userTitleTemplate;67} else {68return titleTemplate;69}70} else {71return titleTemplate;72}73};7475return {76metadata: {77[kIPynbTitleBlockTemplate]: resolveTemplate(),78},79postprocessors: [(output: string) => {80// read notebook81const nb = jupyterFromFile(output);8283// We 'hide' widget metadata from the YAML by encoding it to84// prevent the YAML representation from mangling it. Restore85// it here if it is so hidden86const widgets = nb.metadata.widgets as unknown;87if (widgets && typeof widgets === "string") {88nb.metadata.widgets = JSON.parse(89new TextDecoder().decode(base64decode(widgets)),90);91}9293// convert raw cell metadata format to raw_mimetype used by jupyter94nb.cells = nb.cells.map((cell) => {95if (cell.cell_type == "raw") {96if (cell.metadata[kCellFormat]) {97const format = cell.metadata[kCellFormat];98delete cell.metadata[kCellFormat];99if (format === kTextHtml) {100cell.metadata[kCellRawMimeType] = format;101} else if (format === "tex") {102cell.metadata[kCellRawMimeType] = kTextLatex;103} else if (format === "rst") {104cell.metadata[kCellRawMimeType] = kRestructuredText;105} else if (format === "rtf") {106cell.metadata[kCellRawMimeType] = kApplicationRtf;107} else {108// restore format b/c we didn't convert it109cell.metadata[kCellFormat] = format;110}111}112}113114// Fix up mime types that Quarto has emplaced115cell.outputs?.forEach((output) => {116const cellOutput = output as JupyterOutput;117if (cellOutput.output_type === "display_data") {118const cellDisplayOutput =119cellOutput as JupyterOutputDisplayData;120if (cellDisplayOutput.data["application/json"]) {121const jsonData = cellDisplayOutput122.data["application/json"] as Record<123string,124unknown125>;126if (jsonData[kQuartoMimeType]) {127const realMimetype = jsonData[kQuartoMimeType] as string;128delete jsonData[kQuartoMimeType];129130cellDisplayOutput.data[realMimetype] = jsonData;131delete cellDisplayOutput.data["application/json"];132}133}134}135});136137return cell;138});139Deno.writeTextFileSync(output, JSON.stringify(nb, null, 2));140return Promise.resolve();141}],142};143},144});145}146147148