Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/render/notebook/notebook-contributor-qmd.ts
6465 views
1
/*
2
* notebook-contributor-qmd.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { renderFile } from "../../command/render/render-files.ts";
8
import {
9
ExecutedFile,
10
RenderedFile,
11
RenderServices,
12
} from "../../command/render/types.ts";
13
import {
14
kClearHiddenClasses,
15
kIpynbProduceSourceNotebook,
16
kIPynbTitleBlockTemplate,
17
kKeepHidden,
18
kNotebookPreserveCells,
19
kOutputFile,
20
kRemoveHidden,
21
kTo,
22
kUnrollMarkdownCells,
23
} from "../../config/constants.ts";
24
import { InternalError } from "../../core/lib/error.ts";
25
import { dirAndStem } from "../../core/path.ts";
26
import { ProjectContext } from "../../project/types.ts";
27
import {
28
NotebookContributor,
29
NotebookMetadata,
30
NotebookOutput,
31
} from "./notebook-types.ts";
32
import { error } from "../../deno_ral/log.ts";
33
import { Format } from "../../config/types.ts";
34
import { ipynbTitleTemplatePath } from "../../format/ipynb/format-ipynb.ts";
35
import { projectScratchPath } from "../../project/project-scratch.ts";
36
import { ensureDirSync, existsSync } from "../../deno_ral/fs.ts";
37
import { dirname, join, relative } from "../../deno_ral/path.ts";
38
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";
39
40
export const qmdNotebookContributor: NotebookContributor = {
41
resolve: resolveOutputNotebook,
42
render: renderOutputNotebook,
43
outputFile,
44
cache,
45
cachedPath,
46
};
47
48
function cache(output: NotebookOutput, project?: ProjectContext) {
49
if (project) {
50
// copy the embed into the scratch directory
51
const path = cachePath(output.path, project);
52
ensureDirSync(dirname(path));
53
Deno.copyFileSync(output.path, path);
54
}
55
}
56
57
function cachedPath(nbAbsPath: string, project?: ProjectContext) {
58
if (project) {
59
// see if the embed exists in the scratch directory
60
const output = outputFile(nbAbsPath);
61
const outputPath = join(dirname(nbAbsPath), output);
62
const path = cachePath(outputPath, project);
63
if (existsSync(path)) {
64
return path;
65
}
66
}
67
}
68
69
function cachePath(nbAbsPath: string, project: ProjectContext) {
70
const basePath = projectScratchPath(project.dir, "embed");
71
const outputRel = relative(project.dir, nbAbsPath);
72
return join(basePath, outputRel);
73
}
74
75
function outputFile(
76
nbAbsPath: string,
77
): string {
78
return ipynbOutputFile(nbAbsPath);
79
}
80
81
function resolveOutputNotebook(
82
nbAbsPath: string,
83
_token: string,
84
executedFile: ExecutedFile,
85
_notebookMetadata?: NotebookMetadata,
86
) {
87
const resolved = safeCloneDeep(executedFile);
88
resolved.recipe.format.pandoc[kOutputFile] = ipynbOutputFile(nbAbsPath);
89
resolved.recipe.output = resolved.recipe.format.pandoc[kOutputFile];
90
91
resolved.recipe.format.pandoc.to = "ipynb";
92
93
// TODO: Allow YAML to pass through as raw or markdown block
94
const template = ipynbTitleTemplatePath();
95
96
// Configure echo for this rendering
97
resolved.recipe.format.execute.echo = false;
98
resolved.recipe.format.execute.warning = false;
99
resolved.recipe.format.render[kKeepHidden] = true;
100
resolved.recipe.format.render[kNotebookPreserveCells] = true;
101
resolved.recipe.format.metadata[kClearHiddenClasses] = "all";
102
resolved.recipe.format.metadata[kRemoveHidden] = "none";
103
resolved.recipe.format.metadata[kIPynbTitleBlockTemplate] = template;
104
resolved.recipe.format.render[kIpynbProduceSourceNotebook] = true;
105
resolved.recipe.format.pandoc.citeproc = false;
106
107
// Configure markdown behavior for this rendering
108
resolved.recipe.format.metadata[kUnrollMarkdownCells] = false;
109
return resolved;
110
}
111
async function renderOutputNotebook(
112
nbPath: string,
113
_format: Format,
114
_subArticleToken: string,
115
services: RenderServices,
116
_notebookMetadata: NotebookMetadata | undefined,
117
project: ProjectContext,
118
): Promise<RenderedFile> {
119
const rendered = await renderFile(
120
{ path: nbPath, formats: ["ipynb"] },
121
{
122
services,
123
flags: {
124
metadata: {
125
[kTo]: "ipynb",
126
[kOutputFile]: ipynbOutputFile(nbPath),
127
[kNotebookPreserveCells]: true,
128
[kIpynbProduceSourceNotebook]: true,
129
citeproc: false,
130
},
131
quiet: false,
132
},
133
echo: true,
134
warning: true,
135
quietPandoc: true,
136
},
137
services,
138
project,
139
false, // Don't enforce project constraints on format since this is an intermediary rendering
140
);
141
142
// An error occurred rendering this subarticle
143
if (rendered.error) {
144
error("Rendering of qmd notebook produced an unexpected result");
145
throw (rendered.error);
146
}
147
148
// There should be only one file
149
if (rendered.files.length !== 1) {
150
throw new InternalError(
151
`Rendering an qmd notebook should only result in a single file. This attempt resulted in ${rendered.files.length} file(s).`,
152
);
153
}
154
155
return rendered.files[0];
156
}
157
158
function ipynbOutputFile(nbAbsPath: string) {
159
const [_dir, stem] = dirAndStem(nbAbsPath);
160
return `${stem}.embed.ipynb`;
161
}
162
163