Path: blob/main/src/render/notebook/notebook-contributor-qmd.ts
6465 views
/*1* notebook-contributor-qmd.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { renderFile } from "../../command/render/render-files.ts";7import {8ExecutedFile,9RenderedFile,10RenderServices,11} from "../../command/render/types.ts";12import {13kClearHiddenClasses,14kIpynbProduceSourceNotebook,15kIPynbTitleBlockTemplate,16kKeepHidden,17kNotebookPreserveCells,18kOutputFile,19kRemoveHidden,20kTo,21kUnrollMarkdownCells,22} from "../../config/constants.ts";23import { InternalError } from "../../core/lib/error.ts";24import { dirAndStem } from "../../core/path.ts";25import { ProjectContext } from "../../project/types.ts";26import {27NotebookContributor,28NotebookMetadata,29NotebookOutput,30} from "./notebook-types.ts";31import { error } from "../../deno_ral/log.ts";32import { Format } from "../../config/types.ts";33import { ipynbTitleTemplatePath } from "../../format/ipynb/format-ipynb.ts";34import { projectScratchPath } from "../../project/project-scratch.ts";35import { ensureDirSync, existsSync } from "../../deno_ral/fs.ts";36import { dirname, join, relative } from "../../deno_ral/path.ts";37import { safeCloneDeep } from "../../core/safe-clone-deep.ts";3839export const qmdNotebookContributor: NotebookContributor = {40resolve: resolveOutputNotebook,41render: renderOutputNotebook,42outputFile,43cache,44cachedPath,45};4647function cache(output: NotebookOutput, project?: ProjectContext) {48if (project) {49// copy the embed into the scratch directory50const path = cachePath(output.path, project);51ensureDirSync(dirname(path));52Deno.copyFileSync(output.path, path);53}54}5556function cachedPath(nbAbsPath: string, project?: ProjectContext) {57if (project) {58// see if the embed exists in the scratch directory59const output = outputFile(nbAbsPath);60const outputPath = join(dirname(nbAbsPath), output);61const path = cachePath(outputPath, project);62if (existsSync(path)) {63return path;64}65}66}6768function cachePath(nbAbsPath: string, project: ProjectContext) {69const basePath = projectScratchPath(project.dir, "embed");70const outputRel = relative(project.dir, nbAbsPath);71return join(basePath, outputRel);72}7374function outputFile(75nbAbsPath: string,76): string {77return ipynbOutputFile(nbAbsPath);78}7980function resolveOutputNotebook(81nbAbsPath: string,82_token: string,83executedFile: ExecutedFile,84_notebookMetadata?: NotebookMetadata,85) {86const resolved = safeCloneDeep(executedFile);87resolved.recipe.format.pandoc[kOutputFile] = ipynbOutputFile(nbAbsPath);88resolved.recipe.output = resolved.recipe.format.pandoc[kOutputFile];8990resolved.recipe.format.pandoc.to = "ipynb";9192// TODO: Allow YAML to pass through as raw or markdown block93const template = ipynbTitleTemplatePath();9495// Configure echo for this rendering96resolved.recipe.format.execute.echo = false;97resolved.recipe.format.execute.warning = false;98resolved.recipe.format.render[kKeepHidden] = true;99resolved.recipe.format.render[kNotebookPreserveCells] = true;100resolved.recipe.format.metadata[kClearHiddenClasses] = "all";101resolved.recipe.format.metadata[kRemoveHidden] = "none";102resolved.recipe.format.metadata[kIPynbTitleBlockTemplate] = template;103resolved.recipe.format.render[kIpynbProduceSourceNotebook] = true;104resolved.recipe.format.pandoc.citeproc = false;105106// Configure markdown behavior for this rendering107resolved.recipe.format.metadata[kUnrollMarkdownCells] = false;108return resolved;109}110async function renderOutputNotebook(111nbPath: string,112_format: Format,113_subArticleToken: string,114services: RenderServices,115_notebookMetadata: NotebookMetadata | undefined,116project: ProjectContext,117): Promise<RenderedFile> {118const rendered = await renderFile(119{ path: nbPath, formats: ["ipynb"] },120{121services,122flags: {123metadata: {124[kTo]: "ipynb",125[kOutputFile]: ipynbOutputFile(nbPath),126[kNotebookPreserveCells]: true,127[kIpynbProduceSourceNotebook]: true,128citeproc: false,129},130quiet: false,131},132echo: true,133warning: true,134quietPandoc: true,135},136services,137project,138false, // Don't enforce project constraints on format since this is an intermediary rendering139);140141// An error occurred rendering this subarticle142if (rendered.error) {143error("Rendering of qmd notebook produced an unexpected result");144throw (rendered.error);145}146147// There should be only one file148if (rendered.files.length !== 1) {149throw new InternalError(150`Rendering an qmd notebook should only result in a single file. This attempt resulted in ${rendered.files.length} file(s).`,151);152}153154return rendered.files[0];155}156157function ipynbOutputFile(nbAbsPath: string) {158const [_dir, stem] = dirAndStem(nbAbsPath);159return `${stem}.embed.ipynb`;160}161162163